---
title: "Federating MCP servers"
url: "/docs/mcp-gateway/federation/"
kind: "feature"
summary: "Put Arion in front of upstream MCP servers: auto-discover their tools under a namespace, and control disclosure, caching and refresh."
---

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

# Federating MCP servers

Put Arion in front of upstream MCP servers: auto-discover their tools under a namespace, and control disclosure, caching and refresh.

Dynamic MCP servers let one Arion gateway front a fleet of upstream MCP servers. Arion calls `tools/list` on each upstream, materializes the returned tools into its own registry under that server's namespace, and serves them next to your static tools. Use federation when agents should see one endpoint, with one authentication story and one search index, instead of connecting to every server separately.

## Add a dynamic server

`dynamic_mcp_servers` sits next to `tools` under the filter's `typed_config`:

```yaml,arion=fragment
dynamic_mcp_servers:
  - namespace: github
    description: GitHub repository and pull request tools.
    transport: StreamableHttp
    url: http://127.0.0.1:5678/mcp
    cache_duration: 5m
    tool_disclosure_mode: PROGRESSIVE
```

Discovered tools are exposed under the server's namespace. If the upstream returns `get_pull_request`, agents see `github.get_pull_request`, and Arion sends the original name upstream when it forwards the call. The upstream stays authoritative for input schemas, output schemas and result shapes. A namespace belongs to that server alone: static tools and OpenAPI sources cannot use it.

`tool_disclosure_mode` applies to every tool from the server. Upstream catalogs tend to be large, so the default, `PROGRESSIVE`, keeps them out of `tools/list` and lets agents find them through [tool search](@/docs/mcp-gateway/tool-search.md). Set `EAGER` for a small server whose tools every agent needs.

Always set `transport: StreamableHttp` explicitly. It is the only implemented upstream transport, and the field's default value, `Sse`, fails discovery. `url` must be an absolute `http` or `https` URL. The gateway dials it directly with its own MCP client, so upstream MCP traffic does not pass through a cluster or the route table.

## Caching and refresh

- Tools are fetched when the registry is first used, typically by the first `tools/list`, `tool_search` or code-mode catalog call.
- `cache_duration` is the minimum interval between refreshes; the next list or search after it expires triggers one. If it is unset, the first successful fetch is cached indefinitely.
- A successful refresh replaces that server's tools with the latest list. After a failed refresh, the previous tools keep serving, and Arion waits 30 seconds, or `cache_duration` if it is set, before trying again.
- Every upstream tool name must be a valid local name, and names must be unique. A tool list that breaks either rule is rejected whole, and the previous tools keep serving.
- Tool calls reuse per-session upstream connections and are bounded by the gateway's `upstream_timeout` and `max_upstream_response_bytes`.

## Authorize a whole server

An `rbac` block on a dynamic server applies to every tool materialized from it, which is handy when one upstream serves one tenant or team:

```yaml,arion=fragment
dynamic_mcp_servers:
  - namespace: repo
    description: Repository management tools.
    transport: StreamableHttp
    url: http://repo-mcp.internal:5678/mcp
    cache_duration: 5m
    rbac:
      action: ALLOW
      permissions:
        - jwt_claim:
            field: tenant
            value: tenant-a
```

Per-tool rules use the same shape; see [tool authorization](@/docs/mcp-gateway/tool-authorization.md).

## Pin instead of discover

When you want only a few known tools from an upstream, with stable names and per-tool RBAC, declare them as static tools with an `mcp_server_backend` instead of federating the whole server. See [tools and REST backends](@/docs/mcp-gateway/tools-and-rest-backends.md#forward-a-tool-to-an-mcp-server). Prefer static REST-backed tools when the backend is your own HTTP API and you want Arion to enforce schemas. Prefer federation when the catalog belongs to someone else and changes on their schedule.

## Verify

Search the server's namespace over HTTP; the first search after startup performs the discovery, so it can take one upstream round trip:

```bash
curl -s http://127.0.0.1:8080/mcp/search \
  -H 'content-type: application/json' \
  -d '{"query":"pull request","namespace":"github","limit":5}'
```

The answer contains entries such as `github.get_pull_request`. With `tool_disclosure_mode: EAGER`, they also appear in `tools/list` next to your static tools.

## Limits

- Upstream transport is streamable HTTP only; there is no upstream SSE support.
- Dynamic tools are owned by their server. They are replaced or removed with it and cannot be deleted one at a time, also over [TDS](@/docs/mcp-gateway/tool-discovery-tds.md).
- Namespaces must be unique across servers, and a server's namespace cannot hold static or OpenAPI tools.
- Input and output schema enforcement does not apply to forwarded calls.

Field-by-field details are in the [filter reference](@/reference/filters/http/mcp-gateway.md#dynamic-mcp-servers).
