Zuplo
Model Context Protocol

Expose Internal APIs as Governed MCP Tools

Martyn DaviesMartyn Davies
June 17, 2026
9 min read

Give AI agents your internal API as MCP tools without pasting credentials into editors. Expose chosen operations as an MCP server in Zuplo, then front it with the MCP Gateway for SSO, tool curation, and a per-call audit trail.

Your team wants Claude and Cursor to hit the internal billing API, the inventory service, the admin tooling. The fast way is to mint an API key, paste it into mcp.json, and point an agent at the service directly.

Now a long-lived key with write access to an internal system lives in a dotfile on a laptop, and the agent can call every operation the API exposes, including the ones that delete things.

You don’t have to choose between giving agents the API and keeping it safe. Two Zuplo capabilities chain together: the MCP Server handler turns your API’s routes into MCP tools, the discrete actions an agent like Claude can call, and the MCP Gateway puts those tools behind SSO, strips the dangerous ones, and logs every call. This walkthrough wires them together in the portal.

Best for:
  • Platform and security teams giving agents internal APIs without handing out raw keys
  • API owners ready to expose a safe subset of their service as agent tools
  • Anyone who wants the whole flow in the portal, no config files required

Start with your internal API

This walkthrough assumes your internal API is managed in a Zuplo project, or is about to be: its routes are described by an OpenAPI spec in the project, the way Zuplo manages every gateway. If the API lives elsewhere today, put a Zuplo gateway in front of it and import its OpenAPI spec. Once the routes are in the project, exposing a slice of them as MCP tools takes just a few additional clicks, as you’ll see.

Keep this in its own project. It’s how we split it internally: the API team owns the gateway and the MCP server that sits on it, while platform or security owns the separate gateway project we build later. One account, two projects, and neither team waits on the other to ship.

Turn routes into MCP tools

Open the Code tab, add a route, and pick the Dynamic OpenAPI to MCP Server handler from the dropdown. The portal scaffolds it on POST /mcp and serves it over HTTP, the transport MCP clients connect with. You choose the handler rather than writing it by hand, so the route is wired up without touching JSON.

The Zuplo Route Designer with the Add menu open and the Dynamic OpenAPI to MCP Server handler highlighted as the route type.

You do not surface the whole API. On the route, click Select Tools and tick only the operations agents should reach. A read-only reporting endpoint and a “create draft invoice” call are reasonable; “delete customer” is not.

Each checked operation becomes a tool, named from its operationId and described from its OpenAPI summary, with the input schema derived from the operation so the server validates arguments before your handler runs.

The MCP Tools dialog with listInvoices, getRevenueReport, and createDraftInvoice ticked while sendInvoice and deleteCustomer are left unchecked.

Dynamic MCP Server Quickstart

The full handler flow in text, including local development and every option the handler accepts.

Lock the route with an API key

The MCP server is a normal Zuplo route, so it takes the same inbound policies as any other. For an internal service the simplest control is an API key: open the route’s policy editor and add Zuplo’s API key authentication policy, backed by a key you create in the project. The server then rejects anyone without a valid key. Skip the policy and the route still serves tools, but so does anyone who finds the URL.

The Zuplo route's policy editor with the API key authentication policy added to the inbound pipeline of the MCP server route.

That single key is the only credential that reaches your internal MCP server. What matters is who holds it: not your developers, and not their editors. It goes into the gateway in the next step, which keeps it server-side and never hands it to a client. Your internal API is now reachable as MCP tools, but only by a caller holding a key your developers never see.

Front it with the MCP Gateway

Create a second project in the same account for the gateway. Open its Code tab, click Add Route, and pick MCP Gateway Virtual Server. A virtual server fronts exactly one upstream MCP server, putting it behind your own auth and tool policy, so this one points at the internal MCP server you just built.

The wizard opens on a library of known servers like Linear and Stripe. Yours isn’t in it, so choose the custom MCP server option and paste your internal MCP server’s URL as the upstream. The same wizard, pointed at a third-party server, is the one we walk through in fronting a third-party server; here the upstream is just yours.

The New MCP Gateway Virtual Server wizard on the Upstream step, the Custom MCP server option chosen and the internal MCP server's URL pasted as the upstream.

Gate access with SSO

The wizard’s next step is inbound auth: who is allowed to connect to the gateway. This is where your internal API stops being reachable by anyone with a pasted key and starts being reachable only by your team. Pick the identity provider your organization already runs, Auth0, Okta, Entra, Google, or another OIDC provider, and the same SSO login that gates everything else now gates the MCP server.

The wizard names the environment variables that provider needs, the domain, client ID, and secret, which you add under Settings before anyone connects.

The gateway runs full OAuth on the inbound side. Clients authenticate through your IdP, and the gateway issues its own short-lived token scoped to that one virtual server. The developer’s editor only holds that token, never a credential for the internal API.

Curate tools per team

The wizard’s Tools step decides which of the MCP server’s tools this gateway hands to your team. Passthrough forwards all of them; Curate lists them and lets you tick only the ones you want.

You already trimmed the list once, back on the MCP server. That trim is the ceiling: the most any caller could ever see. Curate narrows it again, just for this gateway, and never touches the API project. So even though the server offers createDraftInvoice, you can stand up a read-only gateway here that exposes only listInvoices and getRevenueReport.

Pro tip:

Leaving a tool off the curated list doesn’t just hide it, it removes it. The gateway rejects any call to a tool you didn’t expose before that call reaches your API, so no cleverly worded prompt can talk an agent into using one.

Broker the key, don’t share it

The wizard’s last step is outbound auth: how the gateway authenticates to your internal MCP server. Your server uses an API key, not OAuth, so make two choices here:

  • None for upstream credentials, so the gateway doesn’t attempt an OAuth exchange with your server.
  • Remove auth token, so the inbound client’s Authorization header is stripped before forwarding and the developer’s token never reaches your internal API.

The wizard's Outbound Auth step with None selected for upstream credentials and Remove auth token selected for the inbound Authorization header.

That clears the inbound token but doesn’t yet present your internal key. Store the key as a secret environment variable under Settings, then add a set-headers policy to the end of the route’s inbound pipeline that injects it as the upstream Authorization header.

The gateway route's policy editor with a set-headers policy at the end of the inbound pipeline, injecting the upstream Authorization header.

The policy reads the key with $env(...), so the value lives only in your secret environment variable and never appears in source control:

JSONjson
{
  "export": "SetHeadersInboundPolicy",
  "module": "$import(@zuplo/runtime)",
  "options": {
    "headers": [
      {
        "name": "Authorization",
        "value": "Bearer $env(INTERNAL_BILLING_API_KEY)"
      }
    ]
  }
}

Connect to an API-key upstream

The full outbound-auth flow, including the set-headers policy config and the secret environment variable.

This is the move that makes the whole thing safe. The internal API key lives in one place, the gateway, and is never copied to a laptop or an editor config. The token a developer’s agent presents and the key that reaches your internal API are two different things, exactly the boundary the MCP authorization spec is built to enforce: an MCP server must not pass through the token it received.

It’s the same boundary we argued for in governing shadow MCP. Rotate the key in one place and every connected agent keeps working.

An AI agent connects over an OAuth token to the Zuplo MCP Gateway in a platform/security project, which curates tools and brokers an API key to the internal MCP server in a separate API-team project, which exposes selected routes of the internal API. The OAuth token stops at the gateway and the API key never leaves it.

Connect your team and audit

Hand your team the gateway URL: the project’s gateway URL plus the route path, something like /mcp/internal-billing. In Claude, that’s Settings, Connectors, Add custom connector, paste the URL, and run the connect flow. The developer authenticates through your IdP once and their agent lists exactly the curated tools, with no key to paste and nothing to leak.

Claude's Connectors view listing only the curated read-only tools from the gateway, each with a per-tool permission control, and no API key in sight.

Alternatively, skip the chat-thread URL and publish the instructions. Every gateway project ships a developer portal, and its MCP setup page generates copy-paste config for Claude, ChatGPT, Codex, Cursor, VS Code, or a generic client, all pointed at your gateway route. Hand your team that page instead of a raw URL.

The gateway project's developer portal showing per-app MCP setup instructions, with tabs for Claude, ChatGPT, Codex, Cursor, VS Code, and Generic, plus a copyable Claude Code CLI command.

Lock that portal down to match the gateway:

  • Whole portal behind login, so only your organization can see it.
  • Individual pages behind protected routes, so the internal-billing instructions reach only the teams that should have them.

Protected routes in the developer portal

Gate portal pages behind authentication so setup instructions reach only the right teams.

Then open Observability, select Analytics, and choose MCP from the sidebar. Every tool call shows up: which operation ran, who invoked it, success rates, and upstream errors. That per-call record is the inventory you never had when developers wired up their own connections, and it’s the same governance you’d want on any MCP server you ship.

The gateway's MCP analytics view: each tool broken out by call volume, error rate, and p95 latency, plus the consumers who invoked them.

Step back and the walkthrough stacked five independent controls between an agent and your internal API. Each one was a single step above, each closes a different gap, and none depends on the others holding:

ControlWhere you set itThe gap it closes
Operation selectionMCP server handler, Select ToolsLimits which operations can ever become tools
SSO inbound authGateway wizard, your IdPKeeps anyone outside your team off the gateway
Tool curationGateway wizard, CurateBlocks curated-out tools before they reach upstream
Key brokeringGateway, set-headers + secret env varStops the internal key landing in an editor config
Per-call analyticsObservability, Analytics → MCPRemoves blind spots about who called which tool

Going to production

Everything you just built lives in your Working Copy, Zuplo’s development environment, which deploys on save and is ideal for testing the connect flow. For production, two changes apply to both projects:

  • Connect source control, so deploys come from a git branch with review and rollback.
  • Put the gateway on a custom domain, so the URL you hand your team is one of yours.

Get started today

The MCP Gateway is in public beta, free to try on a new project. Spin up a free Zuplo project and put your internal API in front of your team’s agents without handing over a single key.