Skip to content

Federation — proxy tools from other MCP gateways (since v2.0 / Phase F10)

The gateway can act as a client to other MCP servers, surface their tools on the local /mcp endpoint under a stable namespace prefix, and forward calls to them. To agents and IDE plugins the federated tools look identical to the gateway's own — same dispatch shape, same lifecycle hooks (F7), same audit trail.

This unblocks the common pattern where teams already run multiple specialised MCP gateways (one per team, one per data domain, one per legacy adapter) and want a single endpoint for their agents to talk to.

Configuring upstreams

Env-only configuration today

Upstreams are configured exclusively via OMCP_FEDERATION_UPSTREAMS — there is no /api/federation runtime-management endpoint yet (planned as a v3.x increment). To change the upstream set, edit the env / Helm value and restart the gateway. Tool calls and metrics already in flight survive the restart on a sticky- ingress / multi-replica deployment.

Set OMCP_FEDERATION_UPSTREAMS to a comma-separated list of name=spec pairs. Each name must start with a letter and use only [a-z0-9_-]. The spec selects the transport:

  • HTTP (default): name=https://upstream.host/mcp — must end at the upstream's Streamable HTTP /mcp endpoint.
  • WebSocket: name=ws://upstream.host/mcp/ws or name=wss://…. Targets the upstream's WebSocket transport. No bearer-auth header is added (the SDK transport only accepts a URL) — embed auth credentials in the URL or front the gateway with an authenticating reverse proxy.
  • Stdio: name=stdio:<command> [args...] — spawn a child process that speaks MCP over its stdio channels. Useful when the upstream is a CLI-style MCP (e.g. an omcp inspector-config instance, a local-only MCP server, a sidecar). Embed literal spaces in arguments by backslash-escaping them.

```bash

HTTP upstream

export OMCP_FEDERATION_UPSTREAMS="payments=https://payments-mcp.internal/mcp,risk=https://risk-mcp.internal/mcp" export OMCP_FEDERATION_TOKEN_PAYMENTS="bearer-for-payments-gw" export OMCP_FEDERATION_TOKEN_RISK="bearer-for-risk-gw"

Mix of HTTP + WebSocket + stdio upstreams

export OMCP_FEDERATION_UPSTREAMS="prod=https://gw/mcp,realtime=wss://gw/mcp/ws,weather=stdio:node /opt/weather-mcp/server.js --quiet" ```

HTTP upstreams' static bearer tokens (forwarded as Authorization: Bearer … on every outbound call) are read from OMCP_FEDERATION_TOKEN_<UPPERCASE-NAME> — separate from the URL list so tokens never appear in logs or audit entries. Stdio upstreams don't carry tokens — they're local-only.

Tool naming

Every upstream tool is registered locally as <upstream-name>.<upstream-tool-name>. For the config above:

text payments.list_open_invoices payments.charge_card risk.score_transaction risk.list_blocked_merchants

Clients see these names on tools/list exactly as if the gateway implemented them natively. Per-credential allow-lists, Products, and RBAC apply to them the same way they apply to native tools — they flow through registerTool, so:

  • F1 Product-allow-list gate (ctx.allowedTools) decides whether a given session even sees them on tools/list.
  • F7 lifecycle hooks (tool_pre_invoke, tool_post_invoke) fire around every federated call.
  • Audit entries record the federated tool with its namespaced name; cross-reference with the upstream's own audit log via the timestamp
  • actor.

Failure mode

  • Initial connect fails → upstream lands in degraded, exposes zero tools, the gateway boots normally. A background retry is not yet wired (re-deploy to re-connect).
  • Mid-run call fails → the proxy returns the upstream's JSON-RPC error verbatim, the caller sees it as a normal MCP error. No retry — let the agent decide.
  • Catalog refresh fails → previous-known-good catalog stays in place, no tool churn. Logged as a warning.

The auto-refresh interval defaults to 5 minutes (the upstream may add or remove tools between polls). Set refreshIntervalMs: 0 per-source to disable (manual refresh only) — exposed via config-yaml integration in a follow-up; today it's the constant default.

Capabilities currently shipped

Feature Status
Streamable HTTP upstream
Stdio upstream follow-up
WebSocket upstream follow-up
Static bearer auth
Caller-OIDC passthrough follow-up (needs per-request identity hand-off)
UAID forwarding follow-up
Auto catalog refresh (5min default)
Manual /api/federation/<name>/refresh follow-up
Redis-backed cross-replica catalog cache follow-up (uses F8 SessionStore once wired)
sources.yaml shape (vs env vars) follow-up
UI "Add Upstream" modal follow-up
Per-source audit-entry upstream: field follow-up (currently audit logs the namespaced name)

The follow-ups are tracked under F10b in the sprint plan; nothing in the current shape is breaking for the deferred items.

Operational notes

  • Loop prevention — the gateway does not advertise federation in its own /api/conformance. An upstream that's itself a federated gateway works fine, but be careful with circular topologies (A federates B, B federates A) — the tool name namespace prevents recursion, but the dispatch latency stacks.
  • Token rotation — set the new token in OMCP_FEDERATION_TOKEN_<NAME> and restart the gateway. Hot rotation via /api/federation is on the follow-up list.
  • Per-tool dispatch latency = local HTTP round-trip + upstream dispatch. Federation typically adds 20-80ms vs a direct call; surface this in your client's perceived latency budget.