Configuring the MCP Gateway
The MCP Gateway is configured the same way as the rest of a Zuplo project: an OpenAPI route file, a policy library, and a runtime plugin registration. Every project that uses the gateway has the same four pieces in source control.
This page shows the four pieces every Zuplo project needs to act as an MCP Gateway, then walks through a minimal single-upstream example. Once that works, the remaining pages in this section cover the handler reference, the multi-upstream pattern, local development, and the compatibility date requirement.
What you can configure
Every runtime option is available in code config — version-controlled in your repo, reviewed through pull requests, and deployed through your existing CI/CD pipelines:
- Capability filtering. Use the
mcp-capability-filter-inboundpolicy to allow-list tools, prompts, resources, and resource templates per route, and to rewrite the descriptions and annotations the upstream advertises. - Manual upstream OAuth client registration. Set
clientRegistration: { mode: "manual" }onmcp-token-exchange-inboundwith a pre-registeredclientId(and optionalclientSecret) when your organization manages OAuth apps centrally, when the upstream provider requires a specific approved client, or whenever Dynamic Client Registration and OIDC Client ID Metadata Documents aren't an option. - Composing with other Zuplo policies. Add
set-headers-inbound,set-upstream-api-key-inbound,rate-limit-inbound, or any custom policy to an MCP route. Anything that runs in the standard Zuplo request pipeline composes withMcpProxyHandler. - Shared-OAuth upstream connections. Use
authMode: "shared-oauth"on the token exchange policy when one upstream credential serves all users on the route. - Protected Resource Metadata overrides. Some upstream MCP servers publish
their PRM at a non-default path; the token exchange policy's
protectedResourceMetadataUrloption overrides the derived default.
The four required pieces
Every Zuplo project that acts as an MCP Gateway wires up four things.
1. Pin the compatibility date
MCP Gateway features require compatibilityDate >= 2026-03-01 in zuplo.jsonc:
Code
See Compatibility dates for details.
2. Register the MCP Gateway plugin
Add a modules/zuplo.runtime.ts file that registers McpGatewayPlugin:
Code
The plugin registers the OAuth metadata, authorization endpoints, consent page, and upstream connect callbacks the gateway needs. It's a no-op when no MCP-related policy is present, so adding it to projects that don't yet use the gateway has zero runtime cost.
3. Define one OAuth policy in policies.json
The OAuth policy authenticates inbound MCP requests against your identity provider and turns the project into an OAuth-protected MCP resource.
Use mcp-auth0-oauth-inbound when the identity provider is Auth0:
Code
For any other OIDC provider (Okta, Microsoft Entra ID, Cognito, Keycloak, etc.),
use the generic mcp-oauth-inbound policy with explicit oidc.* and
browserLogin.* options.
A project can have only one MCP OAuth policy. The gateway rejects any configuration with two, regardless of the policy variant. The same policy is attached to every MCP route in the project — every route authenticates against the same identity provider.
4. Define one mcp-token-exchange-* policy per upstream
Each upstream MCP server gets its own mcp-token-exchange-inbound policy. The
policy resolves the user's upstream credential and attaches it as an
Authorization: Bearer header before the gateway proxies the request:
Code
Naming convention: name each policy mcp-token-exchange-<id>. The id after the
prefix identifies the upstream in analytics and connect URLs. Changing the id
strands any existing user-to-upstream connections, so pick it once and keep it.
5. Define one route per upstream
Each upstream gets a route in routes.oas.json. The handler points at the
upstream URL; the inbound policy chain attaches the OAuth policy followed by the
matching token exchange policy:
Code
get,post is Zuplo's multi-method shorthand. The handler rejects GET with
405 Method Not Allowed because the gateway only speaks stateless Streamable
HTTP over POST — see McpProxyHandler for the full
behavior.
The operationId requirement
Every MCP route must set operationId. It identifies the MCP route and
appears as the virtualServerName in analytics events.
Uniqueness rules:
- No two MCP routes can share an
operationId. - No two MCP routes can share a path.
- No two
mcp-token-exchange-*policies can share an upstreamid.
If operationId is missing or duplicated, the gateway returns a configuration
error on the first matching request.
Putting it all together
A minimal project with one OAuth provider and one upstream MCP server has exactly these files in source control:
Code
With:
zuplo.jsoncpinning the compatibility date.modules/zuplo.runtime.tsregisteringMcpGatewayPlugin.config/policies.jsondeclaring one OAuth policy and one token exchange policy.config/routes.oas.jsonexposing one/mcp/<slug>route that wires the two policies ontoMcpProxyHandler..env(not committed) holdingAUTH0_DOMAIN,AUTH0_CLIENT_ID,AUTH0_CLIENT_SECRET, and any other upstream-specific environment variables.
Start the project with zuplo dev and the gateway is reachable at
http://127.0.0.1:9000/mcp/linear-v1. See
Local development for the dev-loop specifics,
including the loopback-only login shortcut that skips your IdP during
development.
Adding more upstreams
The pattern is the same: one MCP OAuth policy stays shared across the project,
one mcp-token-exchange-* policy and one route get added per new upstream MCP
server. Per-user state is keyed by (subjectId, upstreamServerId), so each user
maintains independent connections to each upstream they consent to.
For a worked example with two upstreams and the file layout, see Add multiple upstream MCP servers.
Next steps
McpProxyHandlerreference — every option, every behavior of the route handler.- Compatibility dates — why
2026-03-01is required and what older dates break. - Local development — dev-loop, loopback URLs, the
/oauth/dev-loginshortcut, environment variables, the workerd restart quirk. - Multi-upstream pattern — one project, many upstream MCP servers.
mcp-capability-filter-inbound— restrict and re-project the tools, prompts, and resources a route exposes.