Skip to content

Expand an OpenAPI 3.x document into one REST-backed MCP tool per operation, with operation filters, one shared policy and live updates over TDS.

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 } } }
OperationToolCall becomes
GET /pets/{petId}petstore.getPet, arguments petId, verbose?GET /api/v3/pets/42?verbose=true
POST /petspetstore.createPet, argument bodyPOST /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, so GET /users/{id} becomes get_users_id. Two operations that end up with the same name are an error.
  • Description. The operation's summary is used, or else its description, 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/schemas references 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, 2XX and default becomes 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/v3 here. The scheme and host are ignored, because requests go to cluster. Set path_prefix to 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/json content.
  • $ref targets outside the document's own components.

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. $ref must point inside the same document.
  • One cluster and one policy per source.
  • The spec's security, per-operation servers and 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.

Continue

Start typing to search the site.

↑ ↓ to move Enter to open Esc to close