An OpenAPI tool source turns every operation in an OpenAPI 3.x document into a REST-backed MCP tool under one namespace. Use it when an API already has a spec: one entry replaces a hand-written tool per endpoint, and the spec's parameters and schemas become the tools' input and output schemas.
Before you begin
- An OpenAPI 3.0 or 3.1 document in JSON or YAML, inline or in a file. Swagger 2.0 documents are rejected.
- A cluster that reaches the API. Every derived tool sends its requests through that cluster, just as a hand-written REST backend does.
Enable it
Add an entry to open_api_tool_sources under the filter's typed_config:
open_api_tool_sources:
- namespace: petstore
cluster: petstore_api
tool_disclosure_mode: EAGER
exclude_operations: ["deletePet", "DELETE /admin*"]
spec:
filename: /etc/arion/petstore.yaml
Arion expands the document once, when the configuration loads, and logs the tools it derived. Every tool is petstore.<operation>. tool_disclosure_mode decides whether the tools are listed in tools/list (EAGER) or found through tool search (PROGRESSIVE, the default). For a large API, leave them progressive.
How operations become tools
Take this document as an example:
openapi: 3.1.0
servers:
- url: https://petstore.example.com/api/v3
paths:
/pets/{petId}:
get:
operationId: getPet
summary: Get one pet
parameters:
- { name: petId, in: path, required: true, schema: { type: string } }
- { name: verbose, in: query, schema: { type: boolean } }
/pets:
post:
operationId: createPet
summary: Create a pet
requestBody:
required: true
content:
application/json:
schema: { type: object, properties: { name: { type: string } } }| Operation | Tool | Call becomes |
|---|---|---|
GET /pets/{petId} | petstore.getPet, arguments petId, verbose? | GET /api/v3/pets/42?verbose=true |
POST /pets | petstore.createPet, argument body | POST /api/v3/pets with body as the JSON body |
- Name. The name is the
operationId, with any character other than a letter, digit,_or-replaced by_. An operation without one is named from its method and path, soGET /users/{id}becomesget_users_id. Two operations that end up with the same name are an error. - Description. The operation's
summaryis used, or else itsdescription, or else the tool name. - Arguments. Path parameters are always required. Query parameters are required when the spec says so. A JSON request body becomes one argument named
body. Parameter descriptions carry over, and#/components/schemasreferences are copied into the tool's schema. - Request. Each path parameter is percent-encoded as a single segment, so a value cannot change the path. An array query argument becomes a repeated parameter.
- Output. The first JSON response schema among
200,201,2XXanddefaultbecomes the tool's output schema when it describes an object. The tool then returns structured content, which code mode scripts receive as parsed JSON. Other responses come back as text. - Base path. It is the path of the first server URL,
/api/v3here. The scheme and host are ignored, because requests go tocluster. Setpath_prefixto use a different base path, for example when the server URL has variables, which Arion does not substitute.
Choose operations
include_operations and exclude_operations take glob patterns. * matches any run of characters, including /, and ? matches one character. Each pattern is tried against the raw operationId and against the METHOD /path form, such as GET /pets/{petId}, and must match the whole string:
include_operations: ["GET *", "createPet"] # every read, plus one write
exclude_operations: ["* /admin*"] # applied after include
An empty include list selects every operation, and exclusions always win. Patterns match the name as written in the spec, not the sanitized tool name.
Unsupported operations
Some operations cannot become a REST tool:
- Deprecated operations.
- Required header or cookie parameters. Optional ones are dropped.
- Path parameters that are not a plain scalar.
- Query parameters other than a scalar or an exploded array of scalars.
- Request bodies without
application/jsoncontent. $reftargets outside the document's owncomponents.
By default the first such operation fails the whole configuration, and the error names the operation. Exclude it, or set skip_unsupported_operations: true to skip unsupported operations with a warning each.
One policy per source
rbac, upstream_policy (authority, timeout, retry_policy) and tool_disclosure_mode apply to every tool derived from the source. To give some operations a different policy, split the document into two sources with different namespaces and disjoint include_operations. Arion does not act on the spec's security section, so credentials for the API must come from the cluster or the upstream policy.
Update over TDS
A control plane can deliver the same entry as an OpenApiToolSource resource named after its namespace. Sending it again replaces that namespace's tools with the new expansion; removing it evicts them. An invalid document is rejected, and the previous tools keep serving. See tool discovery over xDS.
Verify
Search the namespace over HTTP, with no session needed:
curl -s http://127.0.0.1:8080/mcp/search \
-H 'content-type: application/json' \
-d '{"query":"pets","namespace":"petstore","limit":5}'
The answer lists petstore.getPet and petstore.createPet with their generated inputSchema. Because the example sets EAGER, both tools also appear in tools/list.
Limits
- OpenAPI 3.x only.
$refmust point inside the same document. - One cluster and one policy per source.
- The spec's
security, per-operationserversand server variables are ignored. - The document is expanded when the configuration loads or the TDS resource arrives. Editing the file on disk changes nothing until the configuration is reloaded.
- A namespace used by an OpenAPI source cannot also hold static tools or a dynamic server.
The field table is in the filter reference.