Zuplo
MCP Server

Your APIs, as MCP tools

Mark operations in your OpenAPI spec, attach the MCP handler, and ship a remote MCP server on the same edge POPs as your gateway. Tools, resources, and prompts derived from your existing routes. Same OAuth, API keys, rate limits, and audit logging you trust for APIs today.

API Routes

GET/orders
POST/tickets
GET/users/{id}
Zuplo
Zuplo

MCP Tools

list_orders()
create_ticket()
get_user_status()
Claude
ChatGPT
Gemini
Why this matters

Every MCP server in production today is also someone's auth and rate-limiting side project

Standing up an MCP server is the easy part. Making it production-grade — auth, rate limits, observability, deploy pipeline — is where most teams stall. The gateway already solved all of this for your APIs. MCP should inherit it.

×

Hand-rolling an MCP server in Python at 2am

Every product team writing a one-off MCP server. Half-implemented JSON-RPC. Auth handled with environment variables and good intentions. The agent works once, hangs the second time, and nobody knows why.

×

Two definitions, two truths

Your OpenAPI spec says one thing. The MCP tool schema says another. Drift compounds, the agent picks the wrong tool, the customer experience degrades, and the team blames the LLM.

×

Auth and rate limits, reinvented per agent

Each MCP server hand-codes its own auth, its own rate limits, its own logging. Every team's interpretation is slightly different. Compliance review is a sprint. The CISO is unhappy.

×

“Production” is a localhost stdio process

The MCP server runs on a developer's laptop because the team hasn't figured out remote hosting. Every customer demo is a local dev process. Real production for AI agents is still a wishlist item.

What you get

MCP that inherits your gateway, not invents a parallel one

OpenAPI is the source of truth

Mark operations with `x-zuplo-route.mcp` and Zuplo derives tool descriptions, parameters, and schemas from your existing OpenAPI definitions. No second schema to maintain. No drift between API and MCP.

Same policy stack as your APIs

OAuth, API keys, rate limits, prompt-injection detection, audit logs — anything you can attach to a normal route works on the MCP route. There is no separate MCP-only policy mechanism to learn or maintain.

Streamable HTTP at the edge

Zuplo runs the MCP server on the same 300+ edge POPs as your APIs. One POST endpoint, JSON-RPC 2.0, no stdio process to manage. Claude Desktop, Cursor, ChatGPT, MCP Inspector all connect to the same URL.

Streamable HTTP · OpenAPI-driven

One handler, two flavors of tools

Use OpenAPI operations directly when REST is the right shape. Drop into TypeScript when the agent needs orchestration the API surface doesn't model — and call other gateway routes from inside the handler so each step runs through its own policies.

JSONOpenAPI → MCP tool
# routes.oas.json (excerpt)
"/orders/{id}": {
  "get": {
    "operationId": "getOrder",
    "summary": "Fetch order by ID",
    "x-zuplo-route": {
      "mcp": { "type": "resource" }
    }
  }
},
"/tickets": {
  "post": {
    "operationId": "createTicket",
    "summary": "Open a support ticket",
    "x-zuplo-route": {
      "mcp": { "type": "tool" }
    }
  }
}

# Register on the MCP handler:
"/mcp" route → mcpServerHandler({
  operations: [
    { file: "routes.oas.json", operationId: "getOrder" },
    { file: "routes.oas.json", operationId: "createTicket" }
  ]
})
TypeScriptCustom MCP tool · TypeScript
import { ZuploRequest, ZuploContext } from "@zuplo/runtime";

// Register in routes.oas.json with x-zuplo-route.mcp = { type: "tool" }
// and x-zuplo-route.handler = $import(./modules/plan-trip)

export default async function planTrip(
  request: ZuploRequest,
  context: ZuploContext,
) {
  const { destination, dates } = await request.json();

  // Compose existing routes — each runs through its own policies
  const weather = await context.invokeRoute(
    `/v1/weather?city=${destination}`,
  );
  const flights = await context.invokeRoute(
    `/v1/flights?dest=${destination}&dates=${dates}`,
  );
  const hotels = await context.invokeRoute(
    `/v1/hotels?city=${destination}&dates=${dates}`,
  );

  return Response.json({ weather, flights, hotels });
}
Streamable HTTP
Tools / resources / prompts
OAuth · API keys
Rate limits · audit logs
Prompt-injection detection
Claude · Cursor · ChatGPT
What makes Zuplo different

Production MCP, not a Python prototype

Tools, resources, and prompts from one handler

The `mcpServerHandler` exposes all three MCP primitives. Mark a GET route as a resource and clients see it via `resources/list` / `resources/read`. Mark an action as a tool and clients see it via `tools/list` / `tools/call`. Prompts work the same way. One handler, three discovery paths.

Custom tools when OpenAPI isn't enough

Write a TypeScript handler, register it as an MCP tool, orchestrate multi-step workflows by calling other gateway routes via `context.invokeRoute(...)`. Each invoked route runs through its own policies. Build agents that compose your existing API surface without leaving the gateway.

MCP-aware OAuth resource metadata

Set `oAuthResourceMetadataEnabled: true` and Zuplo serves the MCP-spec-compliant OAuth resource metadata clients need to discover your auth server. No custom proxy code. No "works in Cursor but not Claude Desktop" debugging — auth follows the spec.

Govern many MCP servers with MCP Gateway

When the org grows past one MCP server, the MCP Gateway adds auth translation, virtual MCP servers with team-scoped tool access, security policies across all MCP traffic, and observability into every tool invocation — including third-party MCP servers your agents reach.

Real questions, real answers

What teams use this for

“We want Claude to read order data and create support tickets.”

Mark `GET /orders/{id}` as `mcp: { type: "resource" }`. Mark `POST /tickets` as `mcp: { type: "tool" }`. Register both in the MCP handler. Point Claude Desktop at your `/mcp` URL. Done — same OpenAPI, two new MCP capabilities, full audit log on every invocation.

“We need to expose our API to ChatGPT and Cursor users.”

One Zuplo MCP server, one URL, every Streamable-HTTP-capable MCP client connects. ChatGPT (via OpenAI Apps SDK), Cursor, Claude Desktop, MCP Inspector — same endpoint, same auth, same rate limits.

“The agent needs a multi-step workflow our REST API doesn't model.”

Write a TypeScript handler. Mark it `mcp: { type: "tool" }`. Inside, call other gateway routes via `context.invokeRoute(...)` — search products, check inventory, calculate shipping, create order — each one running through its own policies. The agent sees one tool; you keep API hygiene.

“Compliance wants every MCP tool call audited.”

MCP requests pass through the same policy stack as everything else, so attach the same audit-log policy. Every `tools/call` lands in your SIEM with caller identity, tool name, parameters, and response code. No separate logging pipeline for AI traffic.

Frequently Asked Questions

Common questions about hosting MCP servers on Zuplo.

Stop hand-rolling MCP servers

Free Zuplo project, mark a few OpenAPI operations, ship a real edge-deployed MCP server with auth, rate limits, and audit logs in an afternoon.