MCP Gateway quickstart (Local Dev)
Choose your Development Approach
Select how you'd like to build your gateway. You can switch between approaches at any time.
Local Development
Develop and test your gateway locally using the Zuplo CLI. Full control over your environment.
Build a Zuplo MCP Gateway fronting Linear, running locally at
http://127.0.0.1:9000/mcp/linear-v1. By the end, Claude Desktop connects over
the gateway's per-user OAuth flow and answers "list my open Linear issues" with
real results.
Any Zuplo project becomes a gateway by adding a plugin, a couple of policies, and a route. This guide uses Linear as the upstream and the built-in dev-login shortcut for sign-in, so you skip identity-provider setup to try it out. For production, swap in your provider: the gateway wraps Auth0, Okta, Microsoft Entra, Google, Clerk, Cognito, Keycloak, Logto, OneLogin, PingOne, and WorkOS, plus a generic OIDC fallback. See the provider catalog.
Prefer the browser with no local setup? The Portal quickstart reaches the same result through the Zuplo Portal UI.
Prerequisites
-
Node.js 20 or higher.
-
A local Zuplo project. Create an empty one with:
CodeThen
cdinto the new directory. Seecreate-zuplo-apifor other options, or import an existing portal project by connecting it to Git and cloning it.
New projects created with create-zuplo-api ship a recent compatibilityDate,
so MCP Gateway features work out of the box. If you're adding the gateway to an
older project and the build complains about the compatibility date, see
Compatibility dates.
-
Register the MCP Gateway plugin
Open
modules/zuplo.runtime.ts(create it if it doesn't exist) and registerMcpGatewayPlugin:modules/zuplo.runtime.tsThe plugin registers the OAuth metadata, authorization endpoints, consent page, and upstream connect callbacks the gateway needs.
-
Add an OAuth policy with the dev-login shortcut
Setting up a real identity provider for local development is friction. You'd register a loopback 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.Open
config/policies.jsonand add the generic OAuth policy pointed at the dev-login URL:config/policies.json/oauth/dev-loginreturns403 Forbiddenfor any request that doesn't arrive over loopback, so it's safe to leave configured, but only useful in local dev. Production deployments should use a real OIDC provider through one of the IdP wrappers. A common pattern is keeping two OAuth policies (one for production, one for dev) and selecting between them inroutes.oas.jsonby environment.When you do switch to a real provider, its policy reads credentials from
$env(...)references. Define those values in a.envfile at the project root:.env.envis read whennpm run devstarts, so restart the dev server after adding or changing a variable. Never commit.env. Check in a.env.examplewith placeholder values instead. The dev-login shortcut above needs no environment variables, so you can skip this until you wire up a provider. -
Add a token-exchange policy for the upstream
Each OAuth-protected upstream gets its own
mcp-token-exchange-inboundpolicy. It looks up the user's upstream credential and attaches it as the upstreamAuthorizationheader. Add this entry toconfig/policies.json:config/policies.jsonauthMode: "user-oauth"means each user connects their own Linear account the first time they call the route.clientRegistration: { "mode": "auto" }lets the gateway register itself with Linear's OAuth server on demand, so no upstream client credentials in source control. -
Add the route
Open
config/routes.oas.jsonand add an MCP route. The handler points at Linear's MCP server URL; the inbound policy chain runs the OAuth policy followed by the token-exchange policy:config/routes.oas.jsonoperationIdis the stable identifier for the route. It appears in analytics and is part of the per-user upstream connection key, so pick it once and don't change it. The path is whatever you set;/mcp/<provider>-v<n>is the convention. -
Run the gateway
From the project root:
CodeThe route is now reachable at
http://127.0.0.1:9000/mcp/linear-v1.Checkpoint: confirm the OAuth policy is wired up
Send an unauthenticated POST and expect a
401:CodeThe response should be
401 Unauthorizedwith aWWW-Authenticate: Bearerheader pointing at/.well-known/oauth-protected-resource/mcp/linear-v1. That 401 confirms the OAuth policy is loaded. If you see a 200, 404, or 500 instead, the OAuth policy isn't attached to the route.Use 127.0.0.1, not localhost
OAuth metadata and callback URLs key off the request origin. Other loopback aliases (
localhost,::1) can break OAuth subtly in local dev. See Local development for the full set of local-only details, including the knownworkerdrestart quirk. -
Connect Claude Desktop
Open Claude Desktop, go to Settings → Connectors, scroll to the bottom, and click Add custom connector. Paste
http://127.0.0.1:9000/mcp/linear-v1and click Add.Claude Desktop opens the gateway's OAuth flow in a browser:
- The dev-login shortcut signs you in without any IdP prompt.
- The gateway's consent page lists Linear with a Connect button.
- Click Connect, complete Linear's OAuth flow, then click Authorize to finish.
Checkpoint: Claude is connected
Back in Claude Desktop, the new connector appears in Settings → Connectors marked as connected. Subsequent requests reuse the tokens the gateway just issued.
For per-client setup details, see Connect MCP clients.
-
Test it
In Claude Desktop, prompt the model with something that requires Linear. "list my open issues" works well. Claude asks for permission to call the tool, then returns results proxied through the gateway.
You now have a working MCP Gateway in front of Linear, running locally: Claude Desktop signs in through the dev-login shortcut, the gateway exchanges that for a per-user Linear token, and every call is proxied through. The same shape (one OAuth policy, one token-exchange policy per upstream, one route per upstream) scales out to as many upstream MCP servers as you want to front.
Deploy to production before sharing
The local gateway on 127.0.0.1 is for development only, and the dev-login
shortcut works over loopback alone. Before giving others access, swap in a real
identity provider and ship the gateway through the Zuplo Portal. See
environments for setting up a production
deployment.
Next steps
- Deploy from the Portal: swap the dev-login shortcut for a real identity provider and ship the gateway through the Zuplo Portal.
- Local development: the dev-login shortcut in depth, environment variables, and local-only quirks.
- Connect more clients: Claude Code, Cursor, VS Code, ChatGPT, and any other MCP client.
- How it works: the request lifecycle and the two OAuth surfaces.
- Add more upstreams: front several upstream MCP servers from one Zuplo project.
- Capability filtering: curate the tools, prompts, and resources each route exposes.