Model Context Protocol · Multi-tenancy · Security

Build a Multi-Tenant MCP Gateway Without Collapsing Trust Boundaries

Route remote MCP traffic by authenticated tenant while isolating tokens, sessions, discovery, quotas, results, and audit records.

Published
Updated
Code examples
Illustrative
Reading time
7 minutes

A multi-tenant MCP gateway should authenticate the caller, derive an internal tenant context, authorize the requested upstream server and capability, forward only audience-correct credentials, and isolate every session, cache, Task, quota, and log by tenant. It must not become a generic proxy that lets model-controlled input choose arbitrary destinations or headers.

MCP does not define a “gateway” role in its core architecture. This design is an application pattern built on the 2025-11-25 lifecycle, authorization, and Streamable HTTP rules. Preserve those protocol semantics rather than inventing a second, incompatible handshake.

Define the gateway's job narrowly

MCP client
   │ bearer token for gateway resource
   ▼
gateway
   ├── authenticate and derive principal + tenant
   ├── authorize upstream and capability
   ├── enforce origin, size, time, and quota limits
   ├── select an allowlisted upstream route
   ├── acquire an upstream-specific credential
   └── correlate sessions, Tasks, and audit events
             │
             ▼
       upstream MCP server

The gateway may provide policy enforcement, central discovery, protocol compatibility, or private network access. It should not inspect and rewrite arbitrary model content unless that behavior is part of an explicit, tested contract. Every rewrite creates compatibility and integrity risk.

If clients can connect directly and each upstream already implements the required identity and policy, a gateway may add more failure modes than value.

Derive tenant context from verified identity

Authenticate the client using the MCP HTTP authorization flow. Construct an immutable request context only after validating the token's issuer, audience, lifetime, and scopes:

type GatewayContext = Readonly<{
  principalId: string;
  tenantId: string;
  clientId: string;
  scopes: ReadonlySet<string>;
  requestId: string;
}>;

Do not trust a tenantId in tool arguments, an HTTP header supplied by an untrusted client, a resource URI, or MCP _meta. Those may select among already authorized objects, but the server-side context remains authoritative.

If one principal belongs to several tenants, require an explicit, server-validated tenant selection before initialization and bind it to the connection or session. Do not let the model switch tenants mid-tool-call.

Use an allowlisted route table

Never accept an arbitrary upstream URL from an MCP request. Resolve a stable route name through controlled configuration:

type UpstreamRoute = Readonly<{
  routeId: string;
  tenantId: string;
  endpoint: URL;
  expectedResource: string;
  allowedCapabilities: ReadonlySet<string>;
  credentialProfile: string;
  maxRequestBytes: number;
  timeoutMs: number;
}>;

At configuration time, require HTTPS outside local development, reject embedded credentials and fragments, and constrain hosts and ports. At connection time, defend against DNS rebinding and private-address resolution if routes may reference externally managed names. Apply redirects only under a strict same-origin or allowlist policy.

The Streamable HTTP specification requires MCP servers to validate incoming Origin headers and recommends loopback binding for local servers. A gateway facing browsers must enforce that rule at its own edge; an upstream's validation does not protect the gateway endpoint (MCP transport security warning).

Keep credentials audience-bound

The client's token for the gateway is not automatically valid for an upstream. Forwarding it is token passthrough and can violate audience restrictions. Instead, choose one explicit trust model:

  1. Gateway as upstream workload: the gateway uses its own short-lived upstream token. Upstream audit identifies the gateway, while signed internal context or an authorization decision conveys permitted end-user scope.
  2. Token exchange or delegation: an authorization system issues a short-lived upstream token for the exact audience and delegated subject.
  3. Per-user upstream grant: the gateway retrieves a separately acquired upstream credential bound to this principal and tenant.

Whichever model you use, the upstream must validate a token issued for itself. The MCP authorization specification requires resource indicators and audience validation, and explicitly rejects accepting or transiting tokens intended for other resources (MCP token handling).

Never put upstream credentials into MCP messages, model context, URL query strings, session IDs, or logs.

Partition lifecycle and session state

MCP initialization negotiates one protocol version and capability set for a connection. A gateway has two separate legs: client-to-gateway and gateway-to-upstream. Do not claim an upstream capability to the client unless the selected route supports it and the gateway correctly relays it.

If routes differ by tenant, effective capabilities may differ too. Snapshot the negotiated route and capability set for the session so a mid-session configuration update does not silently change semantics.

Use composite internal keys:

(tenant_id, route_id, gateway_session_id)
(tenant_id, route_id, upstream_session_id)
(tenant_id, route_id, task_id)

Do not index by MCP session ID or Task ID alone. Session IDs are identifiers, not secrets or authorization. When relaying Mcp-Session-Id, maintain an explicit mapping rather than exposing one tenant's upstream identifier to another.

Streamable HTTP can be stateful or stateless. If you run multiple gateway replicas, store session mappings in a consistent shared store or use routing that demonstrably keeps a session on the owning replica. Define expiry and cleanup for client disconnects, upstream termination, and gateway deploys.

Filter discovery without breaking contracts

Central policy may hide tools, prompts, or resources a principal cannot use. Filter complete list items after receiving upstream data; do not mutate schemas casually. A changed input schema means the gateway now owns a new public contract and must validate and translate both directions.

Cache discovery only under a key that includes:

  • tenant and route;
  • principal or authorization policy version when lists are identity-dependent;
  • negotiated protocol version; and
  • upstream definition version or an expiry.

Process list-change notifications by invalidating the correct partition. A global tool-list cache is a cross-tenant leak waiting to happen.

Authorization still applies at call time. Hiding submit_refund from discovery improves least exposure, but it does not replace checking the scope, tenant, invoice, approval, and idempotency key when a request names that tool directly.

Isolate results, Tasks, and resumable streams

Tool results and resources may contain tenant data. Bound result size before buffering, redact only according to an explicit contract, and never share response caches unless the cache key proves equivalent authorization and tenant context.

For experimental MCP Tasks, store principal and tenant with the task at creation. Apply the same predicate to get, list, result, and cancellation. If the gateway translates task IDs, use opaque gateway IDs mapped to tenant-bound upstream IDs. The Tasks security section requires isolation and access control.

For resumable SSE, event IDs must be scoped to the correct session. Authenticate every reconnect and verify that Last-Event-ID belongs to that tenant, route, and session before replaying any event.

Apply hierarchical quotas and backpressure

Enforce limits at several levels:

LevelExample controls
RequestBody bytes, execution timeout, result bytes
PrincipalCalls per interval, concurrent tools
TenantDaily cost, open sessions, queued work
RouteUpstream concurrency, circuit breaker
GatewayMemory, file descriptors, total streams

Reserve capacity before starting expensive work and release it in finally logic. A tenant should not exhaust the gateway's SSE connections, workers, or upstream allowance. Return bounded retry information without revealing another tenant's activity.

Rate-limit initialization, discovery, and invalid requests as well as tool calls. Attackers can consume resources before business logic begins.

Produce an audit trail without copying secrets

Record:

  • request and trace identifiers;
  • authenticated principal, client, and tenant;
  • selected route and negotiated protocol version;
  • MCP method and tool/resource/prompt identifier;
  • authorization decision and policy version;
  • duration, byte counts, outcome, and error class; and
  • Task or session identifiers in a restricted audit store.

Avoid logging bearer tokens, raw sensitive arguments, full resources, or model-visible results by default. Use field-level classification and allowlisted summaries. Audit access should itself be tenant-aware and monitored.

Test the boundaries that usually fail

  • A valid token has the wrong audience.
  • A user supplies another tenant's object or Task ID.
  • Two tenants use the same upstream session or task identifier.
  • An upstream route resolves to a private address unexpectedly.
  • A redirect leaves the allowlisted origin.
  • Capability lists differ by identity and a cache entry is reused.
  • An SSE reconnect supplies another session's event ID.
  • A tenant exhausts concurrency while others continue.
  • The upstream changes protocol version or drops a capability.
  • A gateway deploy occurs while stateful sessions are active.

Release checklist

  • Derive principal and tenant from verified identity.
  • Resolve only allowlisted upstream routes.
  • Use audience-correct upstream credentials; prohibit passthrough.
  • Partition sessions, Tasks, caches, quotas, and logs by tenant and route.
  • Advertise only capabilities the gateway can faithfully relay.
  • Authorize at discovery and again at operation time.
  • Authenticate reconnects and scope event replay.
  • Enforce hierarchical size, time, concurrency, and cost limits.
  • Keep sensitive payloads out of normal telemetry.
  • Run explicit cross-tenant negative tests before every release.