---
title: "MCP gateway"
url: "/reference/filters/http/mcp-gateway/"
kind: "reference"
summary: "Arion HTTP filter that serves a Model Context Protocol endpoint, maps tools onto REST, OpenAPI and MCP upstreams, and runs agent scripts in code mode."
---

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

# MCP gateway

Arion HTTP filter that serves a Model Context Protocol endpoint, maps tools onto REST, OpenAPI and MCP upstreams, and runs agent scripts in code mode.

| Attachment | Type URL (`@type`) | Per-route override | Build feature |
|---|---|---|---|
| HTTP filter chain (`http_filters`) | `type.googleapis.com/arion.extensions.filters.http.mcp.mcp_gateway.v3.McpGateway` | `disabled` only | None; `codemode` for `code_mode` |

## What it does

The MCP gateway is an Arion extension that answers Model Context Protocol traffic over streamable HTTP on `/mcp` and owns a registry of namespaced tools. Tools come from static REST or MCP backends, from [OpenAPI documents](@/docs/mcp-gateway/openapi-tools.md) and from [dynamic MCP servers](@/docs/mcp-gateway/federation.md), and [TDS](@/docs/mcp-gateway/tool-discovery-tds.md) streams more over xDS. In tools mode agents list and call the tools directly. In [code mode](@/docs/mcp-gateway/code-mode.md) they send scripts to a single `execute` tool. The [overview](@/docs/mcp-gateway/overview.md) explains the moving parts; this page is the field reference.

## Minimal configuration

This fragment is an entry in `http_filters`, before the router:

{% raw %}
```yaml,arion=fragment
- name: arion.filters.http.mcp
  typed_config:
    "@type": type.googleapis.com/arion.extensions.filters.http.mcp.mcp_gateway.v3.McpGateway
    server_info:
      name: arion-mcp-gateway
      version: "1.0.0"
    tools_mode: {}
    tools:
      - namespace: weather
        name: get_forecast
        description: Get the weather forecast for a city
        disclosure_mode: EAGER
        input_schema:
          inline_string: >-
            {
              "type": "object",
              "properties": { "city": { "type": "string" }, "days": { "type": "integer" } },
              "required": ["city"]
            }
        rest_backend:
          cluster: weather_api
          method: GET
          path: "/v1/forecast/{{city | encode_path_segment}}"
          query_params:
            - name: days
              source: days
```
{% endraw %}

## Top-level fields

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `server_info.name` | string | Yes | — | MCP server identity. Must not be empty or contain `/`, and must be unique per Arion instance when `tds` is set. |
| `server_info.version` | string | Yes | — | Version reported to clients. |
| `tools_mode` / `code_mode` | oneof | Yes | — | The surface agents see. `tools_mode: {}` serves the registry as MCP tools. `code_mode` serves the single `execute` tool; see [code mode](#code-mode). A config with neither fails with `Missing field: mode`. |
| `tools` | list | No | empty | Static tools; see [tools](#tools). |
| `open_api_tool_sources` | list | No | empty | OpenAPI documents expanded into one REST tool per operation; see [OpenAPI tool sources](#openapi-tool-sources). |
| `dynamic_mcp_servers` | list | No | empty | Upstream MCP servers whose tools are discovered at runtime; see [dynamic MCP servers](#dynamic-mcp-servers). |
| `search` | object | No | BM25, 5 results | Tool search ranking and result limit; see [search](#search). |
| `tds` | `{config_name}` | No | unset | Subscribes this filter to a Tool Discovery Service stream. `config_name` must not be empty or contain `/`. |
| `upstream_timeout` | duration | No | `15s` | Timeout for upstream requests that set none of their own. Must be greater than zero. |
| `max_upstream_response_bytes` | uint64 | No | `1048576` | Cap on a REST response body or a serialized MCP tool result. Must be greater than zero. |

## What belongs to which mode

The registry and its sources are shared between the modes; the mode decides the surface agents see. When a field matters in only one mode, this table and the notes in each section say so.

| Configuration | Tools mode | Code mode |
|---|---|---|
| `tools`, `open_api_tool_sources`, `dynamic_mcp_servers`, `tds` | Fill the registry | Fill the registry |
| `disclosure_mode` / `tool_disclosure_mode` | Decides `tools/list` versus `tool_search` | Ignored: the `catalog` sees every permitted tool |
| `search` | Ranks `tool_search` and `POST /mcp/search` | Ranks `catalog.tools.search` |
| `rbac` | Hides and guards tools | Also filters the `catalog` and toolkit visibility |
| `code_mode` limits | — | Sandbox and run limits; see [code mode](#code-mode) |
| `code_mode.toolkits` | — | Curated bundles in the `execute` description; see [toolkits](#toolkits) |
| `POST /mcp/search` | Sessionless search | `404` |

### tool_search arguments

In tools mode, `tool_search` is served automatically when the caller has at least one permitted progressive tool. It takes:

| Argument | Type | Required | Default | Description |
|---|---|---|---|---|
| `query` | string | Yes | — | Free text, ranked against tool names and descriptions with BM25 or the configured embeddings model. |
| `namespace` | string | No | all namespaces | Restrict matches to one namespace. |
| `limit` | uint32 | No | `search.max_allowable_results` | Most results to return, clamped to `search.max_allowable_results`. |

It returns the best-matching progressive tools as full MCP definitions (`name`, `description`, `inputSchema`, `outputSchema`) in the result's text content. `POST /mcp/search` takes the same three arguments as its JSON body. In code mode, the counterpart is `catalog.tools.search` in the [SDK reference](@/reference/sdk/code-mode-sdk-and-limits.md).

## Tools

Every tool is addressed by its qualified name, `namespace.name`. That name appears in `tools/list`, in `tools/call`, in code-mode scripts and in metrics. Each advertised tool also carries `_meta: {"arion/namespace": "<namespace>"}`.

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `namespace` | string | Yes | — | Letters, digits, `_` and `-`. A namespace belongs to exactly one kind of source: static or TDS tools, one OpenAPI source, or one dynamic server. |
| `name` | string | Yes | — | Local name: letters, digits, `_`, `-` and `.`. The qualified name must be unique and at most 128 bytes. For an MCP server backend, this is the upstream tool name. |
| `description` | string | Yes | — | Description shown to models and indexed by search. |
| `input_schema` | data source | No | empty | JSON Schema (`inline_string`, `filename`, …), compiled at load and enforced on every call. Ignored, with a warning, for MCP server backends. |
| `output_schema` | data source | No | empty | JSON Schema for results. When set on a REST tool, responses are parsed, validated and returned as structured content. Ignored for MCP server backends. |
| `rest_backend` / `mcp_server_backend` | oneof | Yes | — | Where calls go; fields below. `function_graph_backend` is not supported. |
| `rbac` | object | No | unset | Per-tool authorization; see [tool RBAC](#tool-rbac). |
| `embedding` | list of float | No | empty | Precomputed vector for remote-embedding search. Must be finite and match `search.embeddings.dimensions`. |
| `disclosure_mode` | enum | No | `PROGRESSIVE` | Tools mode only. `EAGER` lists the tool in every `tools/list`. `PROGRESSIVE` leaves it out; agents find it with `tool_search` and can call it by name at any time. The code-mode catalog ignores it. See [tool search](@/docs/mcp-gateway/tool-search.md). |

### REST backend

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `cluster` | string | Yes | — | Arion cluster the rendered HTTP request is sent to, resolved directly without the route table. |
| `method` | string | Yes | — | HTTP method. |
| `path` | string | Yes | — | Request path. Double-brace placeholders take call arguments. The `encode_path_segment` filter (`{% raw %}{{id \| encode_path_segment}}{% endraw %}`) percent-encodes a scalar value for one path segment. |
| `query_params` | list | No | empty | `{name, source}` pairs. `source` is a dot path into the arguments. Absent values are omitted, and an array becomes repeated parameters. |
| `body_template` | data source | No | unset | Body template rendered from the arguments. Sent as `application/json`. |
| `body_json` | string | No | unset | Dot path to one argument whose value is serialized as the JSON body. If the argument is absent, no body is sent. Mutually exclusive with `body_template`. |
| `upstream_policy.authority` | string | No | unset | Host/`:authority` to send. Required for `ORIGINAL_DST` clusters. |
| `upstream_policy.timeout` | duration | No | `upstream_timeout` | Per-tool override of the gateway-wide timeout. |
| `upstream_policy.retry_policy` | object | No | unset | Route-style retry policy for the upstream call. |

See [tools and REST backends](@/docs/mcp-gateway/tools-and-rest-backends.md) for the transcoding rules.

### MCP server backend

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `transport` | enum | No | `Sse` | `Sse` or `StreamableHttp`. Only `StreamableHttp` is implemented for upstream connections, so set it explicitly. |
| `url` | string | Yes | — | Absolute `http` or `https` URL of the upstream MCP endpoint. The gateway's MCP client dials it directly, with no cluster involved. |

## OpenAPI tool sources

Each entry expands an OpenAPI 3.x document into one REST-backed tool per operation, named `{namespace}.{operationId}`, when the configuration loads. See [OpenAPI tools](@/docs/mcp-gateway/openapi-tools.md) for the mapping rules.

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `spec` | data source | Yes | — | The OpenAPI 3.x document, JSON or YAML. Swagger 2.0 is rejected. |
| `namespace` | string | Yes | — | Namespace of every derived tool: letters, digits, `_` and `-`. Must not be shared with another source. |
| `cluster` | string | Yes | — | Cluster every derived tool calls. |
| `upstream_policy` | object | No | unset | `authority`, `timeout` and `retry_policy`, applied to every derived tool. |
| `rbac` | object | No | unset | Applied to every derived tool. |
| `tool_disclosure_mode` | enum | No | `PROGRESSIVE` | Disclosure mode of every derived tool; tools mode only, as `disclosure_mode`. |
| `path_prefix` | string | No | path of `servers[0].url` | Base path placed before every operation path. Replaces the spec's server path. |
| `skip_unsupported_operations` | bool | No | `false` | By default, an operation that cannot become a tool fails the whole configuration. When `true`, it is skipped with a warning. |
| `include_operations` | list of string | No | empty (all) | Glob patterns (`*`, `?`) matched against an operation's `operationId` and its `METHOD /path` form. |
| `exclude_operations` | list of string | No | empty | Same patterns, applied after `include_operations`. |

## Dynamic MCP servers

Each entry is polled with `tools/list`, and every discovered tool joins the registry as `{namespace}.{upstream tool name}`.

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `namespace` | string | Yes | — | Namespace of this server's tools, and the server's identity over TDS. |
| `description` | string | No | — | Human description. |
| `transport` | enum | No | `Sse` | Same values and caveat as the MCP server backend. |
| `url` | string | Yes | — | Absolute `http` or `https` URL of the upstream MCP endpoint. |
| `cache_duration` | duration | No | unset | Minimum interval between refreshes. If unset, a successful first fetch is cached indefinitely. A failed fetch is retried after 30 seconds, and the last good tool list keeps serving meanwhile. Must be greater than zero. |
| `rbac` | object | No | unset | Applied to every tool materialized from this server. |
| `tool_disclosure_mode` | enum | No | `PROGRESSIVE` | Disclosure mode of every tool materialized from this server; tools mode only, as `disclosure_mode`. |

## Tool RBAC

`rbac` guards tool calls and hides unauthorized tools from listing, search and the code-mode catalog. It uses the JWT validated by [JWT authentication](@/reference/filters/http/jwt-authn.md); details are in [tool authorization](@/docs/mcp-gateway/tool-authorization.md).

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `action` | enum | No | `ALLOW` | `ALLOW` grants access on a matching permission; `DENY` blocks on one. |
| `permissions` | list | Yes | — | At least one `jwt_claim: {field, value}` or `jwt_header: {field, value}`. Any match decides. |

## Search

`search` tunes how `tool_search`, `POST /mcp/search` and the code-mode `catalog.tools.search` rank tools; see [tool search](@/docs/mcp-gateway/tool-search.md).

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `max_allowable_results` | uint32 | No | `5` | The most results one search returns. A caller's `limit` defaults to this value and is clamped to it. Must be greater than zero. |
| `embeddings` | object | No | unset | OpenAI-compatible embeddings endpoint. When omitted, tools are ranked with the built-in BM25 scorer. |
| `embeddings.cluster` | string | Yes | — | Cluster used to reach the endpoint. |
| `embeddings.model_id` | string | Yes | — | Model id sent in the request body. |
| `embeddings.path` | string | No | `/v1/embeddings` | Request path on the endpoint. |
| `embeddings.timeout` | duration | No | `5s` | Request timeout. |
| `embeddings.dimensions` | uint32 | Yes | — | Vector dimension produced by the model. Must be greater than zero. |
| `embeddings.max_batch_size` | uint32 | No | `64` | Most tools sent in one embeddings request. Must be greater than zero. |

## Code mode

{{ <badge label="requires: codemode" kind="requires" /> }}

`code_mode` replaces the tool list with the single `execute` tool. A binary built without the `codemode` feature rejects it. Numeric fields left at `0` take their defaults. The script API and the full list of limits are in the [code mode SDK reference](@/reference/sdk/code-mode-sdk-and-limits.md).

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `execution_timeout` | duration | No | `30s` | Wall-clock limit for one run. Must be greater than zero. A caller's `timeout_ms` can only lower it. |
| `max_host_calls` | uint32 | No | `100` | Tool calls, catalog calls and log lines allowed in one run. |
| `max_concurrent_executions` | uint32 | No | `32` | Runs at once on this listener. Further calls are refused, not queued. Must not exceed `runtime.codemode.pool_capacity`. |
| `max_concurrent_tool_calls` | uint32 | No | `8` | Tool and catalog calls in flight within one run. Extra calls wait. |
| `max_output_bytes` | uint64 | No | `1048576` | Cap on the final result and on each call result a script receives. At least 1024. |
| `memory_limit_bytes` | uint64 | No | `67108864` | Sandbox memory for one run. Must not exceed `runtime.codemode.max_memory_bytes`. |
| `engine_artifact_path` | string | No | bundled | Path to a replacement sandbox component. Its imports are checked when the listener loads. |
| `toolkits` | list | No | empty | Curated tool bundles offered to scripts; fields below. |

### Toolkits

A toolkit appears in the `execute` description and in `catalog.toolkits` only for callers who may use every tool it names.

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `name` | string | Yes | — | Unique name: letters, digits, `_`, `-` and `.`. |
| `description` | string | Yes | — | The kind of task the toolkit is for, shown in the `execute` description. |
| `instructions` | string | Yes | — | Guidance on composing the tools, returned by `catalog.toolkits.get`. |
| `tools` | list | Yes | — | At least two distinct `{namespace, name}` references. A missing tool hides the toolkit instead of failing the configuration. |

## Endpoints

| Request | Mode | Answer |
|---|---|---|
| `POST /mcp` | Both | JSON-RPC: `initialize`, `ping`, `tools/list`, `tools/call`. Every request after `initialize` carries `mcp-session-id`. |
| `DELETE /mcp` | Both | Ends the session. |
| `GET /mcp` | Both | `405`. There is no SSE stream. |
| `POST /mcp/search` | Tools mode | Sessionless search: body `{query, namespace?, limit?}`, answer `{"tools": [...]}` over every permitted tool. `404` in code mode. |

## Ordering and interactions

Place the filter after [CORS](@/reference/filters/http/cors.md) and after JWT authentication, because tool RBAC needs the validated claims, and before the router. Tool calls dispatch to their configured clusters, and the route table handles only requests the filter does not, so a plain fallback route is enough. TDS resources arrive as `{server_info.name}/{config_name}/{resource_name}`; see [TDS resources](@/reference/xds/tds-resources.md). Tool invocations, including every call a code-mode script makes, are counted in the [`mcp_*` metrics](@/reference/observability/metrics-list.md#mcp-gateway-mcp) under the qualified tool name.
