---
title: "OpenAPI tools"
url: "/docs/mcp-gateway/openapi-tools/"
kind: "feature"
summary: "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."
---

> Arion site index: [/llms.txt](/llms.txt)

# OpenAPI tools

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](@/docs/mcp-gateway/tools-and-rest-backends.md) does.

## Enable it

Add an entry to `open_api_tool_sources` under the filter's `typed_config`:

```yaml,arion=fragment
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](@/docs/mcp-gateway/tool-search.md) (`PROGRESSIVE`, the default). For a large API, leave them progressive.

## How operations become tools

Take this document as an example:

```yaml,name=petstore.yaml
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, 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](@/docs/mcp-gateway/code-mode.md) 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:

```yaml,arion=fragment
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](@/docs/mcp-gateway/tool-discovery-tds.md).

## Verify

Search the namespace over HTTP, with no session needed:

```bash
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](@/reference/filters/http/mcp-gateway.md#openapi-tool-sources).
