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

Local development

The MCP Gateway runs the same way locally as any Zuplo project — zuplo dev, port 9000, hot reload on file changes. A few details are specific to the gateway: the gateway prefers 127.0.0.1 over localhost, OAuth login can be short-circuited entirely in dev, and the local workerd worker needs a full restart after some MCP client connect attempts.

Start the gateway

From the project root:

TerminalCode
zuplo dev

The gateway listens at http://127.0.0.1:9000. Each MCP route in routes.oas.json becomes reachable at that origin — for example http://127.0.0.1:9000/mcp/linear-v1.

Prefer 127.0.0.1 over localhost

OAuth metadata, callback URLs, and the in-dev login shortcut all key off the request origin. Other loopback aliases (localhost, ::1, [::1]) can cause subtle OAuth issues in local dev.

When configuring an MCP client locally, use 127.0.0.1:

Code
// Good "url": "http://127.0.0.1:9000/mcp/linear-v1" // Avoid in local dev — works for most things, breaks subtly for OAuth "url": "http://localhost:9000/mcp/linear-v1"

The same applies to any callback or redirect URI you configure with an identity provider for local testing.

Bypass your IdP with /oauth/dev-login

Setting up a real OIDC provider for local development is friction — you'd have to register a localhost callback, manage test users, and so on. The gateway exposes a loopback-only shortcut that skips the IdP round-trip entirely and signs you in as a fixed dev-browser-user subject.

To use it, set browserLogin.url to the dev-login URL when configuring the OAuth policy:

Code
// config/policies.json — using the generic mcp-oauth-inbound policy { "name": "dev-oauth", "policyType": "mcp-oauth-inbound", "handler": { "module": "$import(@zuplo/runtime/mcp-gateway)", "export": "McpOAuthInboundPolicy", "options": { "oidc": { "issuer": "http://127.0.0.1:9000", "jwksUrl": "http://127.0.0.1:9000/.well-known/jwks.json", }, "browserLogin": { "url": "http://127.0.0.1:9000/oauth/dev-login", }, }, }, }

When browserLogin.url points at /oauth/dev-login, the browserLogin.tokenUrl, browserLogin.clientId, and browserLogin.clientSecret options aren't required. The consent page renders normally.

The /oauth/dev-login route returns 403 Forbidden for any request that doesn't arrive over loopback. It's not a security risk to leave configured for production, but it's also not useful — production deployments should use a real OIDC provider through one of the IdP-specific wrappers.

A common pattern is keeping two OAuth policies in the project — one for production (Auth0, Okta, Entra, or any other supported IdP) and one for local dev — and selecting between them in routes.oas.json based on the environment.

Environment variables

When the OAuth policy reads from $env(...) references, define the values in a .env file at the project root:

TerminalCode
# .env # Auth0 wrapper interpolations AUTH0_DOMAIN=your-tenant.us.auth0.com AUTH0_CLIENT_ID=your-auth0-web-app-client-id AUTH0_CLIENT_SECRET=your-auth0-web-app-client-secret # Optional: the audience the gateway requires on issued tokens AUTH0_AUDIENCE=https://mcp-gateway.example.com

.env is read at zuplo dev startup. Restart the dev server after adding or changing an environment variable.

Never commit .env to source control. Instead, check in a .env.example (or env.example) that documents which variables are required and an empty/placeholder value for each.

Adding the gateway to a local MCP client

Once zuplo dev is running and the route is reachable, add the gateway URL to your MCP client config the same way you'd add any other remote MCP server. For example, with Claude Desktop:

Code
// claude_desktop_config.json { "mcpServers": { "linear-via-zuplo-local": { "url": "http://127.0.0.1:9000/mcp/linear-v1", }, }, }

The client triggers the gateway's OAuth flow on first connect. With /oauth/dev-login configured, the browser tab opens, lands on the consent page without any IdP login, and you connect each upstream through its normal browser OAuth flow. Subsequent calls reuse the issued tokens until they expire.

See Connect MCP clients for client-specific snippets and the connect URL format.

When zuplo dev crashes after a connect attempt

Some MCP client connect attempts can leave the local dev server in a state where hot reload no longer recovers it. If the dev server stops responding after an MCP client connects — particularly after browser OAuth callbacks finish — fully restart zuplo dev:

TerminalCode
# Stop zuplo dev with Ctrl+C # Start it again zuplo dev

Then have the MCP client reconnect. A restart doesn't force a re-consent — your upstream tokens are still stored.

This is a known dev-only quirk and doesn't affect deployed gateways.

Verifying the gateway is up

Two quick checks that don't require an MCP client:

Fetch the well-known OAuth metadata for a route. The path follows the route's operationId:

TerminalCode
curl http://127.0.0.1:9000/.well-known/oauth-protected-resource/mcp/linear-v1

A correct response is JSON with resource, authorization_servers, bearer_methods_supported, and scopes_supported fields.

Send a POST without a token. The gateway should return 401 with a WWW-Authenticate header pointing at the Protected Resource Metadata URL:

TerminalCode
curl -i -X POST http://127.0.0.1:9000/mcp/linear-v1 \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","method":"tools/list","id":1}'

If you see the 401 plus the challenge, the OAuth policy is wired up correctly. The next call from a real client will then start the OAuth dance.

Next steps

  • McpProxyHandler reference — the route handler the gateway uses for proxying.
  • Compatibility dates — pin 2026-03-01 in zuplo.jsonc.
  • Multi-upstream pattern — one project, many upstreams.
  • Connect MCP clients — wire each client to the local or deployed gateway URL.
Edit this page
Last modified on May 27, 2026
Multi-upstreamCapability filtering
On this page
  • Start the gateway
  • Prefer 127.0.0.1 over localhost
  • Bypass your IdP with /oauth/dev-login
  • Environment variables
  • Adding the gateway to a local MCP client
  • When zuplo dev crashes after a connect attempt
  • Verifying the gateway is up
  • Next steps
JSON
JSON
JSON