ZuploZuplo
LoginSign Up
  • Documentation
  • API Reference
Introduction
Getting Started
    Develop using the Portal
      1 - Setup Your Gateway2 - Rate Limiting3 - API Key Auth4 - Deploy5 - Dynamic Rate LimitingMCP - Quick start
    Develop Locally
      1 - Setup Your Gateway2 - Rate Limiting3 - API Key Auth
Concepts
Development
Policies
Handlers
API Keys
MCP Server
MCP Gateway
    IntroductionBetaQuickstartQuickstart (Local Dev)How it works
    Connect MCP clients
    Authentication
    Configuration
      Set up the gatewayMulti-upstreamLocal developmentCapability filteringCurate toolsCurate tools (in code)McpProxyHandlerCompatibility dates
    Observability
    ReferenceTroubleshooting
AI Gateway
Developer Portal
Monetization
Deploying & Source Control
Observability
Networking & Infrastructure
Account Management
Programming API
Build with AI
Zuplo CLI
Migration Guides
Platform LimitsSecuritySupportTrust & ComplianceChangelog
powered by Zudoku
Configuration

McpProxyHandler reference

McpProxyHandler is the route handler that backs every MCP Gateway route. It accepts stateless Streamable HTTP requests over POST, forwards them to the configured upstream MCP server using Zuplo's standard URL rewrite, and emits a pair of analytics events per request so the gateway dashboard knows what each capability call did.

When to use it

Use McpProxyHandler on any route that proxies to an upstream MCP server. Pair it with at least one MCP OAuth policy on the inbound chain; add an mcp-token-exchange-inbound policy when the upstream itself requires OAuth, and optionally mcp-capability-filter-inbound to curate what the upstream advertises.

If the upstream uses a static API key or static header instead of OAuth, keep the MCP OAuth policy on the route, drop the token exchange policy, and add set-upstream-api-key-inbound or set-headers-inbound to attach the credential before the handler forwards.

Configuration

The handler is referenced from the route's x-zuplo-route.handler block in routes.oas.json:

Code
"x-zuplo-route": { "corsPolicy": "none", "handler": { "module": "$import(@zuplo/runtime/mcp-gateway)", "export": "McpProxyHandler", "options": { "rewritePattern": "https://mcp.linear.app/mcp" } }, "policies": { "inbound": [ "auth0-managed-oauth", "mcp-token-exchange-linear" ] } }

Set corsPolicy to "none". MCP clients aren't browser-based and shouldn't be sending ambient credentials.

Options

rewritePattern (required)

The upstream MCP server URL. The handler forwards each authenticated POST to this URL, with the resolved upstream Authorization: Bearer header applied by the token exchange policy.

Two value forms are supported:

  • A literal HTTPS or HTTP URL. Used verbatim as the upstream target.
  • An environment-variable reference of the form ${env.X}. The variable must resolve to a fully-qualified HTTP(S) URL.
Code
// Literal URL { "rewritePattern": "https://mcp.linear.app/mcp" } // Environment variable { "rewritePattern": "${env.UPSTREAM_MCP_URL}" }

Dynamic request-based patterns are explicitly rejected — MCP routes need a stable upstream URL.

The URL Rewrite handler's broader template syntax — ${params.x}, ${headers.get("x")}, and so on — is not supported on rewritePattern for MCP routes. Use a literal URL or an ${env.X} reference.

forwardSearch (optional)

Type: boolean. Default: true.

When true, the inbound request's query string is appended to the upstream URL before forwarding. Set to false to drop client query parameters.

followRedirects (optional)

Type: boolean. Default: false.

When false, redirects from the upstream return as-is to the client (status code and Location header passed through). Set to true to have the runtime follow them transparently.

mtlsCertificate (optional)

Type: string. The id of an mTLS certificate registered with the Zuplo project. When set, the upstream fetch uses mutual TLS with the specified client certificate. Most MCP upstreams don't require mTLS; leave this unset unless you specifically need it.

Behavior

GET returns 405

The gateway only speaks stateless Streamable HTTP, and the MCP authorization spec uses POST for every JSON-RPC call. A GET to an MCP route returns:

Code
HTTP/1.1 405 Method Not Allowed Allow: POST Content-Type: application/problem+json { "type": "https://httpproblems.com/http-status/405", "status": 405, "detail": "MCP Gateway routes support stateless Streamable HTTP requests over POST. Server-sent event GET streams are not supported." }

If you've seen an MCP server that exposes a GET endpoint for SSE event streams, that's a different transport. The Zuplo MCP Gateway is Streamable HTTP, POST-only.

POST forwards to the upstream

A POST request runs through the inbound policy chain, then the handler emits capability analytics events, forwards to the upstream URL, and emits a completion event with outcome, mcpStatus, latencyMs, and any JSON-RPC error details.

Inbound auth headers don't leak to the upstream — the gateway-issued bearer token is stripped, and the token exchange policy sets the upstream's own Authorization: Bearer <upstream-token> header.

Route requirements

Every route that uses McpProxyHandler must:

  • Set operationId. It's used to identify the MCP route.
  • Include an MCP OAuth policy in the inbound chain — one of the IdP-specific wrappers (Auth0, Cognito, Clerk, Entra, Google, Keycloak, Logto, Okta, OneLogin, Ping, WorkOS) or the generic mcp-oauth-inbound.
  • Include at most one mcp-token-exchange-inbound policy.

Across the project:

  • 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 upstream id.

Analytics

Every POST emits two analytics events when the request body parses as a JSON-RPC call:

  • A capability_invocation_started event fired before the upstream fetch, carrying the parsed mcpMethod and capabilityName.
  • A capability_invocation_completed event fired after the response, carrying outcome, mcpStatus, latencyMs, and any JSON-RPC error details.

Each event also includes the route's operationId (as virtualServerName), the upstream id (as upstreamServerName), the authenticated subjectId, the authProfileId, and the upstreamAuthMode. See Analytics for the dashboard view and Logging for the structured-log counterpart.

Related

  • mcp-token-exchange-inbound — resolves the upstream credential and handles upstream 401 refresh and retry.
  • mcp-capability-filter-inbound — curates the upstream surface area on a per-route basis.
  • Multi-upstream pattern — pair one McpProxyHandler route with each upstream MCP server in one project.
Edit this page
Last modified on May 27, 2026
Curate tools (in code)Compatibility dates
On this page
  • When to use it
  • Configuration
  • Options
    • rewritePattern (required)
    • forwardSearch (optional)
    • followRedirects (optional)
    • mtlsCertificate (optional)
  • Behavior
    • GET returns 405
    • POST forwards to the upstream
  • Route requirements
  • Analytics
  • Related
JSON
JSON