ZuploZuplo
LoginStart for Free
  • Documentation
  • API Reference
Getting Started
Concepts
API Management
AI Gateway
MCP Gateway
    Overview
    Getting Started
      How it works
      Connect MCP clients
      Authentication
      Cross App Access
      Configuration
      Policies
      Observability
      ReferenceServer RegistryTroubleshooting
    MCP Server
    Developer Portal
    Development
    Deploying & Source Control
    Analytics
    Observability
    Networking & Infrastructure
    Account Management
    Programming API
    Build with AI
    Zuplo CLI
    Migration Guides
    Platform LimitsVersion Support PolicySecuritySupportTrust & ComplianceChangelog
    powered by Zuplo
    MCP Gateway

    Getting Started with MCP Gateway - Editor

    1. Choose a productMCP Gateway
    2. Choose a workflowLocal with an editor
    3. 3Build and testFollow your tutorial

    MCP Gateway tutorial/Local with an editor

    Connect an MCP client to Linear through your own gateway and call a Linear tool.

    Follow along using

    Zuplo PortalLocal with an editorLocal with an AI agent

    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 24 or higher.

    • A local Zuplo project. Create one with:

      TerminalCode
      npx create-zuplo-api@latest my-api-project

      Then cd into the new directory. See create-zuplo-api for 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.

    Run your MCP Gateway locally

    1. Register the MCP Gateway plugin

      Open modules/zuplo.runtime.ts (create it if it doesn't exist) and register McpGatewayPlugin:

      Code
      import { RuntimeExtensions } from "@zuplo/runtime"; import { McpGatewayPlugin } from "@zuplo/runtime/mcp-gateway"; export function runtimeInit(runtime: RuntimeExtensions) { runtime.addPlugin(new McpGatewayPlugin()); }

      The plugin registers the OAuth metadata, authorization endpoints, consent page, and upstream connect callbacks the gateway needs.

    2. Add an OAuth policy with the dev-login shortcut

      The local dev-login endpoint signs you in as dev-browser-user without an identity provider.

      Open config/policies.json and add the generic OAuth policy pointed at the dev-login URL:

      Code
      { "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/jwks" }, "browserLogin": { "url": "http://127.0.0.1:9000/__zuplo/oauth/dev-login" } } } }

      The policy requires oidc.issuer and oidc.jwksUrl, but dev-login doesn't use them. Keep these placeholder values for this tutorial; the gateway doesn't serve /jwks.

      Before deploying, configure an OIDC identity provider. The dev-login endpoint only accepts requests from loopback origins and returns 403 Forbidden for other origins. See Configure local and deployed environments to use one policy with different environment variables for each environment.

    3. Add a token-exchange policy for the upstream

      Each OAuth-protected upstream gets its own mcp-token-exchange-inbound policy. It looks up the user's upstream credential and attaches it as the upstream Authorization header. Add this entry to config/policies.json:

      Code
      { "name": "mcp-token-exchange-linear", "policyType": "mcp-token-exchange-inbound", "handler": { "module": "$import(@zuplo/runtime/mcp-gateway)", "export": "McpTokenExchangeInboundPolicy", "options": { "displayName": "Linear", "protectedResourceMetadataUrl": "https://mcp.linear.app/.well-known/oauth-protected-resource", "authMode": "user-oauth", "scopes": [], "clientRegistration": { "mode": "auto" } } } }

      authMode: "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.

    4. Add the route

      Open config/routes.oas.json and 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:

      Code
      { "openapi": "3.1.0", "info": { "title": "MCP Gateway", "version": "0.1.0" }, "paths": { "/mcp/linear-v1": { "get,post": { "operationId": "linear-mcp-server", "summary": "Linear MCP Proxy", "x-zuplo-route": { "corsPolicy": "none", "handler": { "module": "$import(@zuplo/runtime/mcp-gateway)", "export": "McpProxyHandler", "options": { "rewritePattern": "https://mcp.linear.app/mcp" } }, "policies": { "inbound": ["dev-oauth", "mcp-token-exchange-linear"] } } } } } }

      operationId is 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.

    5. Run the gateway

      From the project root:

      TerminalCode
      npm run dev

      The project's dev script runs zuplo dev.

      The 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:

      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}'

      The response should be 401 Unauthorized with a WWW-Authenticate: Bearer header 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 the same hostname for local URLs

      Use 127.0.0.1 in your MCP client URL to match the policy in this tutorial. You can use localhost instead, but update both URLs to avoid a callback mismatch. See Local development.

    6. Connect Claude Desktop

      Use a local stdio bridge so Claude Desktop can reach your local gateway. Native remote connectors connect from Anthropic's servers and cannot reach 127.0.0.1 on your computer.

      In Claude Desktop, open Settings → Developer → Edit Config and add this entry to claude_desktop_config.json, preserving any existing servers:

      Code
      { "mcpServers": { "Zuplo MCP": { "command": "npx", "args": [ "-y", "mcp-remote", "http://127.0.0.1:9000/mcp/linear-v1", "--allow-http" ] } } }

      Save the file and restart Claude Desktop. The bridge opens the gateway's OAuth flow on first connection.

      In the browser:

      1. The dev-login shortcut signs you in without any IdP prompt.
      2. The gateway's consent page lists Linear with a Connect button.
      3. Click Connect, complete Linear's OAuth flow, then click Authorize to finish.

      Checkpoint: Claude is connected

      Back in Claude Desktop, the Zuplo MCP server appears under Settings → Developer. Its tools are available in chat. Subsequent requests reuse the tokens the gateway just issued.

      For per-client setup details, see Connect MCP clients.

    7. 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.
    Getting Started with MCP Gateway - PortalAgent
    On this page
    • Prerequisites
    • Run your MCP Gateway locally
      • Register the MCP Gateway plugin
      • Add an OAuth policy with the dev-login shortcut
      • Add a token-exchange policy for the upstream
      • Add the route
      • Run the gateway
      • Connect Claude Desktop
      • Test it
    • Next steps
    TypeScript
    JSON
    JSON
    JSON
    JSON