OpenAPI tools
Generating one callable tool per operation from an OpenAPI 3.x spec with OpenAPIToolset, including credential binding, parameter mapping, and HTTP execution via RestApiTool.
Point OpenAPIToolset at an OpenAPI 3.x spec and get back a Vec<Arc<dyn Tool>> — one RestApiTool per operation, each with a JSON-Schema declaration derived from the spec’s parameters and a run that assembles and sends the real HTTP request. Security schemes in the spec map onto the auth system, so the runner resolves credentials before each call.
OpenAPIToolset
OpenAPIToolset::from_yaml(spec: &str) -> Result<Self>- Parse a YAML or JSON spec string (the YAML parser accepts both).
OpenAPIToolset::from_json(spec: &str) -> Result<Self>- Alias for
from_yaml, for callers who want the intent in the name. OpenAPIToolset::from_path(path: impl AsRef<Path>) -> Result<Self>- Read a spec file from disk and parse it.
fn with_credential(self, scheme_name: impl Into<String>, cred: AuthCredential) -> Self- Bind a credential to one of the spec’s
securitySchemesby name; every operation accepting that scheme gets it attached. fn with_base_url(self, base: impl Into<String>) -> Self- Override the spec’s
servers[0]URL on every operation — e.g. to point at a staging host or a local mock. fn operation_names(&self) -> Vec<&str>- The generated tool names, handy for building filters.
fn build_tools<F: Fn(&ParsedOperation) -> bool>(self, predicate: F) -> Vec<Arc<dyn DynTool>>- Generate one
RestApiToolper operation passing the predicate. fn into_tools(self) -> Vec<Arc<dyn DynTool>>- Generate all tools — shorthand for
build_tools(|_| true).
How operations become tools
- Name — the
operationId, converted to snake_case (getPetById→get_pet_by_id). Operations without an id fall back to a method+path name. - Description — the operation’s
summary, ordescriptionwhen no summary exists. - Parameters — each path/query/header/cookie parameter becomes an
ApiParameterwith a snake_case arg name, aParamLocation, and a JSONSchema;build_args_schema()merges them into a single object schema with the spec’srequiredflags preserved. - Request body — exposed as a single
bodyargument;$refs are resolved andallOfcompositions are merged into one object schema.
RestApiTool execution
At call time, RestApiTool::run validates that every required parameter is present, then assembles the request by ParamLocation: path params are substituted into the URL template with percent-encoding (a b → /pets/a%20b), query params join the query string, header params become headers, cookie params are appended to a Cookie header with RFC 6265 validation (so embedded ; or CRLF cannot smuggle extra cookies), and the body arg is sent as JSON. The response always comes back as {"status": <u16>, "body": <json-or-string>} — non-2xx statuses are returned, not raised, so the model can react to them.
Security schemes → AuthConfig
Each operation’s security requirements are matched against the spec’s securitySchemes. The first scheme with a bound credential yields AuthConfig::new(scheme).with_raw(credential); a recognised scheme with no bound credential still surfaces an AuthConfig, so the runner reports a misconfiguration instead of silently calling unauthenticated. Before run, the runner resolves the config and injects the result into ToolContext::auth_credential; the tool then injects it per scheme type — apiKey into a header, query param, or cookie; http: bearer / basic into Authorization; OAuth2/OIDC access tokens as Bearer.
Petstore walkthrough
This mirrors the repo’s integration test (tests/openapi_petstore.rs), which runs the generated tools against a wiremock backend.
use adk_rs::auth::credential::AuthCredential;
use adk_rs::tools::openapi::OpenAPIToolset;
const SPEC: &str = r#"
openapi: 3.0.0
info: { title: Petstore, version: 1.0.0 }
servers:
- url: https://petstore.example.com
paths:
/pets/{id}:
get:
operationId: getPetById
security: [ { bearerAuth: [] } ]
parameters:
- { name: id, in: path, required: true, schema: { type: integer } }
responses: { '200': { description: ok } }
/pets:
post:
operationId: createPet
requestBody:
required: true
content:
application/json:
schema:
type: object
properties: { name: { type: string } }
required: [name]
responses: { '201': { description: created } }
components:
securitySchemes:
bearerAuth: { type: http, scheme: bearer }
"#;
let tools = OpenAPIToolset::from_yaml(SPEC)?
.with_credential("bearerAuth", AuthCredential::bearer("my-token"))
.into_tools();
// tools[..] are named get_pet_by_id and create_pet
let agent = adk_rs::agents::LlmAgent::builder("petstore")
.model(model)
.instruction("Manage pets via the API tools.")
.tools(tools)
.build()?;When the model emits getPetById-style calls, the agent dispatches get_pet_by_id with {"id": 42}; the tool issues GET https://petstore.example.com/pets/42 with Authorization: Bearer my-token and hands {"status": 200, "body": {...}} back to the model. A create_pet call sends its body argument as the JSON request body.
Filtering and lower-level access
use adk_rs::tools::openapi::{HttpMethod, OpenAPIToolset};
let read_only = OpenAPIToolset::from_path("specs/petstore.yaml")?
.build_tools(|op| matches!(op.method, HttpMethod::Get | HttpMethod::Head));The module also exports the building blocks — parse_spec / SpecParse, ParsedOperation, ApiParameter, ParamLocation, HttpMethod, to_snake_case, and RestApiTool::new(op, auth_config) — if you want to construct tools from hand-built operations.
Related pages
- Auth —
AuthCredential,AuthConfig, and the credential-resolution pipeline. - Tools overview — how generated tools join the dispatch loop.
- Security — the HTTPS-or-loopback rule across the crate.