MCP Server
AI agents such as Claude and Cursor call APIs through tools served over the Model Context Protocol (MCP). RapiDoc turns your OpenAPI spec into an MCP server, and curates it on the way, because exposing every endpoint one-to-one works poorly for agents: internal endpoints leak, transport headers become tool arguments the model has to guess, and every extra tool costs context on every turn.
RapiDoc reads your plain OpenAPI spec, leaves out internal and deprecated operations, strips transport and credential headers from tool arguments, and turns reference-data lookups into MCP resources. The result is an MCP manifest: plain JSON with one tool per operation, real JSON Schema arguments and MCP safety annotations. It ships in a small TypeScript project you can run as-is.
You can create it three ways:
- In the browser: the Export MCP Server button of
<rapi-doc>(or its JavaScript methods). Nothing is sent to a server. - On the command line:
rapidoc mcp exportandrapidoc mcp serve(@rapidoc/cli). - In Node:
@rapidoc/mcp, for build scripts and your own servers.
1. How operations become tools
Section titled “1. How operations become tools”No extra annotations are needed. Every tool and resource comes from standard OpenAPI fields:
- Tool names: The tool name is the snake_case form of the
operationId(placeOrderbecomesplace_order). Operations without anoperationIdget a name derived from the HTTP method and path. Duplicate names get a_2,_3suffix. - Tool descriptions: The operation
summaryis used, else itsdescription, elseMETHOD /path. HTML and Markdown are stripped and the text is truncated to 300 characters. - Left out: Operations marked
x-internal: trueorx-hidden: true, operations markeddeprecated: true, and webhooks are left out. - Headers: Headers like
Authorization,Cookie,X-Api-Key,User-Agentand other transport metadata are stripped from LLM tool arguments, as is any parameter that carries an API key named in the spec’ssecuritySchemes. The runtime applies the right scheme itself, from environment variables, and reads the base URL fromservers[0](overridable withAPI_BASE_URL). - Resources: A
GETwith no required parameters whose path contains a reference-data word (/reference,/currencies,/countries,/tiers,/categories,/status,/schema) becomes an MCP resource instead of a tool. Its URI isreference://and the tool name withoutget_, with dashes (getCurrenciesbecomesreference://currencies). - Safety annotations: Every tool carries MCP annotations:
readOnlyHintforGETonly,destructiveHintforPUT,PATCHandDELETE,idempotentHintforGET,PUTandDELETE. Hosts use them to ask the user before running a consequential tool. - Token budget: RapiDoc estimates how many tokens the tool list adds to every turn, and warns above 30 tools or 5,000 tokens.
2. Example
Section titled “2. Example”Here is a plain OpenAPI 3.x snippet and how each operation is treated:
openapi: 3.0.3info: title: Commerce Fulfillment API version: 2.4.0servers: - url: https://api.example.com/v2 # Base URL in the manifestsecurity: - api_key: [] # Applied by the runtime from API_KEY
paths: # 1. Tool "place_order" (POST: readOnlyHint false) /orders: post: summary: Place a new order operationId: placeOrder parameters: - name: Authorization # Stripped: never a tool argument in: header schema: { type: string } requestBody: required: true content: application/json: schema: type: object required: [customerId, items] properties: customerId: { type: string } items: { type: array, items: { type: string } } shippingMethod: { type: string, enum: [standard, express, overnight] }
# 2. Read-only tool "get_order" /orders/{orderId}: get: summary: Get an order operationId: getOrder parameters: - name: orderId in: path required: true schema: { type: string }
# 3. MCP Resource "reference://currencies" (GET, no required params, reference-data path) /currencies: get: summary: List supported currencies operationId: getCurrencies
# 4. Excluded: internal endpoint /admin/database/vacuum: post: summary: Trigger internal database maintenance x-internal: true
# 5. Excluded: deprecated endpoint /orders/legacy: get: summary: Legacy order list deprecated: true
components: securitySchemes: api_key: type: apiKey in: header name: X-API-Key3. The Manifest
Section titled “3. The Manifest”The export is data, not generated code. manifest.json describes the whole server:
server:name(snake_case of themcp-server-nameattribute or the API title),versionanddescriptionfrominfo.sources: the API, namedapi, with thebaseUrlfromservers[0](server variables filled with their defaults) and the spec’ssecuritySchemescopied as they are. An Arazzo document can add more sources.tools: one per operation that survives the exclusions above.resources: the reference-data lookups, with areference://URI.workflows: one entry per Arazzo workflow when an Arazzo document is given (see Workflows as tools); otherwise empty.
Each tool has a name, a description, an inputSchema, MCP annotations and an operation block that tells the runtime how to make the HTTP call. The inputSchema is real JSON Schema: path, query, header and cookie parameters become properties by name, and the request body becomes a body property holding the full body schema, with nested objects, arrays, enums and required intact. $refs are inlined, cycles are cut, and read-only properties are dropped because they never go in a request.
The place_order tool from the example above:
{ "name": "place_order", "description": "Place a new order", "inputSchema": { "type": "object", "properties": { "body": { "type": "object", "required": ["customerId", "items"], "properties": { "customerId": { "type": "string" }, "items": { "type": "array", "items": { "type": "string" } }, "shippingMethod": { "type": "string", "enum": ["standard", "express", "overnight"] } } } }, "required": ["body"] }, "annotations": { "readOnlyHint": false, "destructiveHint": false, "idempotentHint": false }, "operation": { "source": "api", "method": "POST", "path": "/orders", "params": [{ "name": "body", "in": "body", "contentType": "application/json" }], "security": [{ "api_key": [] }] }}operation.params records where each argument goes (path, query, header, cookie or body), and operation.security is the operation’s security requirement, else the spec’s. Because the manifest holds everything a server needs, servers in other languages can read the same manifest.json later without regenerating anything.
4. The Runtime and the Exported Project
Section titled “4. The Runtime and the Exported Project”The runtime
Section titled “The runtime”runtime.js executes any tool in the manifest with fetch, so the same file runs in Node, browsers and edge workers. createMcpRuntime(manifest, { fetch, credentials, baseUrls }) returns listTools(), listResources(), callTool(name, args) and readResource(uri). A tool call:
- Puts each argument where
operation.paramssays, percent-encoding path parameters and serializing arrays as repeated query parameters. - Applies the security scheme: an API key in a header, query parameter or cookie; HTTP Basic; a Bearer token for HTTP bearer, OAuth 2 and OpenID Connect.
- Sends the request and returns an MCP result:
contentwith the JSON response pretty-printed,structuredContentwhen the response is a JSON object, andisErrorwith the HTTP status and body on a 4xx or 5xx.
What is in the zip
Section titled “What is in the zip”Export MCP Server downloads <server-name>-mcp.zip:
| File | Purpose |
|---|---|
manifest.json |
The manifest described above |
runtime.js |
The runtime for operation tools; identical for every API |
workflow-runtime.js |
Runs workflow tools; present only when the manifest has workflows, identical for every API |
server.ts |
A stdio server on @modelcontextprotocol/sdk that registers the manifest’s tools and resources and hands every call to the runtime; identical for every API |
package.json |
Depends on @modelcontextprotocol/sdk; tsx as a dev dependency; npm start runs tsx server.ts |
README.md |
How to run it and which environment variables it reads |
claude_desktop_config.json |
A ready-made mcpServers entry for Claude Desktop and Cursor |
Running it
Section titled “Running it”unzip commerce-fulfillment-api-mcp.zip -d commerce-fulfillment-api-mcpcd commerce-fulfillment-api-mcpnpm installAPI_KEY="your-key" npm startEnvironment variables
Section titled “Environment variables”Credentials are read from environment variables named after the security schemes in the spec, in upper snake case: the scheme petstore_auth reads PETSTORE_AUTH, api_key reads API_KEY. API_KEY is also the fallback for any scheme without its own variable. HTTP Basic takes user:password in the scheme’s variable, or <NAME>_USERNAME and <NAME>_PASSWORD. API_BASE_URL overrides the manifest’s base URL for every source.
Workflows as tools
Section titled “Workflows as tools”When the page also has a <rapi-flow> element tied to the <rapi-doc> (or you pass --arazzo to rapidoc mcp), the export adds one tool per Arazzo workflow. The manifest’s workflows holds each workflow’s steps with their operations resolved, and the runtime runs them: inputs become the tool’s arguments, every step’s parameters and body are filled from $inputs and earlier steps’ outputs, success criteria are checked (a step without criteria succeeds on a 2xx response), onSuccess / onFailure actions (end, goto, retry) are followed, and the workflow’s outputs are the result. The same runtime powers the Run button of <rapi-flow>, which runs a workflow in the browser with the credentials entered in the tied <rapi-doc> and lights up each step. A workflow that uses something the runtime does not run yet (a step that calls another workflow, regex / jsonpath / xpath criteria) is left out, and generateMcpServer().problems says why.
import { buildMcpManifest, createMcpRuntime, parseArazzo } from '@rapidoc/mcp';
const arazzo = parseArazzo(readFileSync('petstore.arazzo.yaml', 'utf8'));const manifest = buildMcpManifest(spec, { arazzo, onProblem: console.warn });const runtime = createMcpRuntime(manifest, { credentials: { api_key: process.env.API_KEY } });const result = await runtime.callTool('adopt_pet', { quantity: 1 }, { onStep: (step) => console.log(step.stepId, step.outcome, step.response?.status),});5. Creating the server
Section titled “5. Creating the server”In <rapi-doc>
Section titled “In <rapi-doc>”Set the mcp attribute:
<rapi-doc spec-url="/specs/my-api.yaml" mcp="true" mcp-server-name="my-api-mcp"> </rapi-doc>mcp(default"false"):"true"shows an Export MCP Server button in the API overview that downloads the project as a zip."false","none"or an empty value hides it.mcp-server-name(optional): the server’s name. Defaults to the spec’s title.
From JavaScript
Section titled “From JavaScript”You can build the manifest, trigger the download, or check the token budget from code. Every method accepts an options object with serverName, includeInternal and includeDeprecated:
const doc = document.querySelector('rapi-doc');
// 1. Build the manifest, the project files and the token diagnostics in memoryconst result = doc.generateMcpServer({ serverName: 'my_store_mcp' });console.log(result.manifest.tools.map((t) => t.name)); // ['place_order', 'get_order', ...]console.log(result.files['server.ts']); // every file of the zip, path → textconsole.log(result.budget); // { totalTools: 5, estimatedTokens: 850, ... }console.log(result.hostConfig); // Claude Desktop / Cursor config JSON
// 2. Download the project as <server-name>-mcp.zipdoc.downloadMcpServer();
// 3. Inspect token budget overhead before exportingconst budget = doc.getMcpBudget();console.log(`Overhead: ~${budget.estimatedTokens} tokens/turn across ${budget.totalTools} tools.`);result.files has the keys manifest.json, server.ts, runtime.js, package.json, README.md and claude_desktop_config.json, plus workflow-runtime.js when there are workflows. getMcpBudget() returns { totalTools, totalResources, estimatedTokens, consequentialCount, warnings }, where consequentialCount is the number of tools that are not read-only.
From the command line
Section titled “From the command line”npx rapidoc mcp export openapi.yaml writes the same project as the Export button, and npx rapidoc mcp serve openapi.yaml runs the server over stdio without writing files (Command Line).
In Node: @rapidoc/mcp
Section titled “In Node: @rapidoc/mcp”The same builder, runtime and project writer are available without the browser or any element, for build scripts and servers:
npm install @rapidoc/mcpimport { readFileSync } from 'node:fs';import { parse } from 'yaml';import { buildMcpManifest, createMcpRuntime, writeMcpProject } from '@rapidoc/mcp';
const spec = parse(readFileSync('openapi.yaml', 'utf8'));const manifest = buildMcpManifest(spec, { serverName: 'petstore' });
// Call a tool directly, with credentials by security schemeconst runtime = createMcpRuntime(manifest, { credentials: { api_key: process.env.API_KEY } });const result = await runtime.callTool('get_pet_by_id', { petId: 1 });
// Or write the same project the Export button downloadsconst files = writeMcpProject(manifest); // path → text@rapidoc/mcp ships TypeScript types (McpManifest, McpRuntime, …). Its only dependency is yaml (for Arazzo documents); requests use Node’s fetch.
6. Connecting Claude Desktop and Cursor
Section titled “6. Connecting Claude Desktop and Cursor”Every exported project includes a ready-to-use configuration for Claude Desktop and Cursor, with one env entry per security scheme in the spec (API_KEY alone when the spec declares none):
Claude Desktop Configuration
Section titled “Claude Desktop Configuration”Copy the generated snippet to your claude_desktop_config.json, and run the host from the unzipped folder or make the server.ts path absolute:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json
For the Swagger Petstore, which declares the schemes api_key and petstore_auth:
{ "mcpServers": { "petstore_mcp": { "command": "npx", "args": ["-y", "tsx", "server.ts"], "env": { "API_KEY": "your-api-key", "PETSTORE_AUTH": "your-petstore-auth" } } }}