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:
Code
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
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
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 via mcp-auth0-oauth-inbound or mcp-oauth-inbound.
A common pattern is keeping two OAuth policies in the project — one for
production (Auth0 or generic OIDC) 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:
Code
.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
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:
Code
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:
Code
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:
Code
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
McpProxyHandlerreference — the route handler the gateway uses for proxying.- Compatibility dates — pin
2026-03-01inzuplo.jsonc. - Multi-upstream pattern — one project, many upstreams.
- Connect MCP clients — wire each client to the local or deployed gateway URL.