| 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 and from dynamic MCP servers, and TDS streams more over xDS. In tools mode agents list and call the tools directly. In code mode they send scripts to a single execute tool. The overview explains the moving parts; this page is the field reference.
Minimal configuration
This fragment is an entry in http_filters, before the router:
- 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: daysTop-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. A config with neither fails with Missing field: mode. |
tools | list | No | empty | Static tools; see tools. |
open_api_tool_sources | list | No | empty | OpenAPI documents expanded into one REST tool per operation; see OpenAPI tool sources. |
dynamic_mcp_servers | list | No | empty | Upstream MCP servers whose tools are discovered at runtime; see dynamic MCP servers. |
search | object | No | BM25, 5 results | Tool search ranking and result limit; see 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.toolkits | — | Curated bundles in the execute description; see 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.
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. |
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. |
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 ({{id | encode_path_segment}}) 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 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 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; details are in tool authorization.
| 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.
| 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
requires: codemode
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.
| 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 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. Tool invocations, including every call a code-mode script makes, are counted in the mcp_* metrics under the qualified tool name.