v12.2
Model Context Protocol • AI Agent Engineering

Shaping Curated MCP Servers

How RapiDoc applies smart heuristics to a plain OpenAPI spec to cut prompt token tax, keep internal endpoints away from agents, and build an MCP manifest with a runnable TypeScript server directly in the browser.

Zero ConfigWorks on Plain OpenAPI
One ManifestJSON Schema Tools & Annotations
Zero BackendRuns 100% in Browser
npm startRunnable TypeScript Project
Philosophy

Why Naive 1:1 REST-to-MCP Fails

When teams first connect OpenAPI to AI agents, their initial instinct is to convert every REST endpoint into an individual MCP tool. As Jeremiah Lowin explains in "Stop Converting Your REST APIs to MCP", this naive approach severely degrades LLM performance:

Naive 1:1 Conversion

Endpoint-as-a-Tool Trap

  • Token Window Bloat: 30–50 raw JSON schemas consume 4,000–8,000 tokens on every conversation turn.
  • Internal Leaks: Infrastructure endpoints (/healthz, /metrics, /admin/debug) confuse the model.
  • Transport Pollution: Headers like Authorization, Cookie, and X-Api-Key pollute tool parameters.
RapiDoc Curated MCP

Agent-First Tool Design

  • Lean Token Footprint: Reference lookups become MCP Resources, and a live token budget warns before the tool catalog saturates the context.
  • Safety Annotations: every tool carries MCP readOnlyHint, destructiveHint and idempotentHint, so hosts can ask before a POST, PUT, PATCH or DELETE.
  • Zero Admin Leaks: Automatic filtering via x-internal, x-hidden, and deprecated: true.
  • Clean Auth Separation: Transport headers are stripped; the runtime applies the spec's securitySchemes from environment variables.
Performance & ROI

Key Performance Indicators (KPIs)

Unlike generic converters that blindly dump hundreds of endpoints into an agent's context, RapiDoc is engineered to meet strict, quantifiable enterprise production benchmarks:

Token Budget KPI
5,000token guardrail

Context Overhead Control

Skips internal, hidden, deprecated and webhook operations, strips transport headers and moves reference lookups into resources. getMcpBudget() estimates the per-turn overhead and warns above 5,000 tokens or 30 tools.

Estimated live from your spec
Security Scorecard
0credentials in tool arguments

Auth Stays Out of the Model's Hands

Auth headers and API-key parameters are never tool arguments. The runtime applies the spec's securitySchemes from environment variables and percent-encodes every path parameter it substitutes.

API key, HTTP Basic, Bearer, OAuth 2, OpenID Connect
Data, Not Code
1manifest, one runtime

One Manifest Drives Everything

manifest.json holds every tool with real JSON Schema arguments and MCP annotations. runtime.js executes it with fetch in Node, browsers and edge workers, and server.ts is the same file for every API.

Servers in other languages can read the same manifest

Competitive KPI Matrix: RapiDoc vs. Generic MCP Generators

How RapiDoc stacks up against raw 1:1 endpoint wrappers and generic cloud CLI tools:

Performance Dimension / KPIGeneric 1:1 ConvertersGeneric Cloud CLI GeneratorsRapiDoc Curated Generator
Internal & Deprecated EndpointsExposed as toolsPartially filteredSkips x-internal, x-hidden, deprecated and webhooks
OutputGenerated code per APIHosted proxy or heavy CLIOne JSON manifest, one fixed runtime, a runnable TypeScript project
Tool ArgumentsFlat string bagsPartial schemasReal JSON Schema: nested bodies, arrays, enums, required
Safety AnnotationsNoneNoneMCP readOnlyHint, destructiveHint, idempotentHint
AuthenticationKeys as tool argumentsHardcoded static credentialsFrom securitySchemes, via environment variables
Backend / Cloud DependencyExternal Node server requiredPaid cloud SaaS or heavy CLIZero Backend (100% In-Browser & Air-Gapped)
Real-Time Token Budget WarningsNone (Blind to token saturation)NoneBuilt-in turn budget & token saturation guards
Zero Configuration

Smart Heuristics (Out of the Box)

RapiDoc needs no extra annotations. It derives every tool and resource from standard OpenAPI fields during export:

Tool Names & Descriptions

Tool names are the snake_case form of each operationId, or derived from method and path when it is missing. Descriptions come from the summary, else the description, else METHOD /path.

Header & Transport Sanitization

Headers like Authorization, Cookie, X-Api-Key, User-Agent, and Accept are stripped from LLM parameter schemas, as is any parameter that carries an API key named in securitySchemes. The runtime applies the scheme itself, from an environment variable named after it, against the base URL from servers[0] (or API_BASE_URL).

Internal & Deprecated Route Exclusion

Operations marked x-internal: true, x-hidden: true or deprecated: true, and webhooks, are omitted from generated agent tools so models never see admin or stale APIs.

Static Resource Classification

A GET with no required parameters on a reference-data path (/reference, /currencies, /countries, /tiers, /categories, /status, /schema) becomes an MCP Resource (reference://<name>), preserving tool calling budgets.

Safety Annotations

Every tool carries MCP annotations: readOnlyHint for GET, destructiveHint for PUT, PATCH and DELETE, idempotentHint for GET, PUT and DELETE, so agent hosts can ask the user before running a consequential tool.

Token Budget Diagnostics

RapiDoc dynamically estimates prompt context tokens per conversation turn and generates warnings if tool count exceeds 30 or context exceeds 5,000 tokens.

Mapping

From OpenAPI to MCP

In the Model Context Protocol, Tools represent executable actions, while Resourcesrepresent contextual reference data the model can inspect without function calling. Here is how each operation in a plain spec is treated:

openapi.yaml
# Plain OpenAPI: no extra annotations needed
security:
  - api_key: []                      # Applied by the runtime from API_KEY

paths:
  /orders:
    post:
      operationId: placeOrder        # Tool "place_order"
      summary: Place a new order     # Tool description
      parameters:
        - name: Authorization        # Stripped: never a tool argument
          in: header
          schema: { type: string }
      requestBody:                   # Tool argument "body", full JSON Schema
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/NewOrder" }
      # POST: readOnlyHint false, so hosts can ask before running it

  /orders/{orderId}:
    get:
      operationId: getOrder          # Read-only tool "get_order"
      summary: Get an order

  /currencies:
    get:
      operationId: getCurrencies     # Resource "reference://currencies"
      summary: List supported currencies

  /admin/database/vacuum:
    post:
      summary: Trigger internal database maintenance
      x-internal: true               # Excluded

  /orders/legacy:
    get:
      summary: Legacy order list
      deprecated: true               # Excluded

components:
  securitySchemes:
    api_key:                         # Copied into the manifest's sources
      type: apiKey
      in: header
      name: X-API-Key

OpenAPI Field Reference

OpenAPI FieldApplies ToEffect on the Manifest
operationIdOperationTool name in snake_case (placeOrder → place_order); derived from method and path when missing.
summary / descriptionOperationTool description (summary first), stripped of HTML and Markdown and truncated to 300 characters.
x-internal / x-hiddenOperationWhen true, the operation is left out.
deprecatedOperationWhen true, the operation is left out.
HTTP methodOperationSets the tool's annotations: readOnlyHint for GET, destructiveHint for PUT, PATCH and DELETE, idempotentHint for GET, PUT and DELETE.
Parameters and requestBodyOperationThe tool's inputSchema: path, query, header and cookie parameters by name, plus a body property with the request body's full schema ($refs inlined, read-only properties dropped).
PathOperationA parameterless GET on a reference-data path becomes a reference:// resource.
Header parametersParameterAuth and transport headers (Authorization, Cookie, X-API-Key, User-Agent, ...) are not exposed as tool arguments.
serversRoot SpecDefault base URL, overridable with the API_BASE_URL environment variable.
securitySchemes / securityRoot Spec / OperationCopied into the manifest; the runtime reads each scheme's credential from an environment variable named after it (petstore_auth → PETSTORE_AUTH).
Data, Not Code

The Manifest

The export is a plain JSON manifest.json, not generated code. It holds the server (name, version, description), one entry in sources (the base URL from servers[0] and the spec's securitySchemes), the tools, the resources and the workflows of a tied <rapi-flow>, each one a tool of its own. Each operation tool has a real JSON SchemainputSchema, MCP annotations and an operation block that tells the runtime where every argument goes:

manifest.json (one tool)
{
  "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": [] }]
  }
}
inputSchema

Real JSON Schema arguments

Path, query, header and cookie parameters by name, plus a body property with the request body's full schema: nested objects, arrays, enums and required. $refs are inlined, cycles cut, read-only properties dropped.

annotations

MCP safety hints

readOnlyHint for GET, destructiveHint for PUT, PATCH and DELETE, idempotentHint for GET, PUT and DELETE.

operation

How to make the call

source, method, path, params with each argument's location, and the operation's security requirement (else the spec's).

Run It

One Runtime, One TypeScript Project

Export MCP Server downloads <server-name>-mcp.zip: the manifest plus files that are identical for every API. Run it with npm install and npm start, or copy the mcpServers entry from claude_desktop_config.json into Claude Desktop or Cursor:

terminal
# The zip: manifest.json, runtime.js, server.ts, package.json, README.md, claude_desktop_config.json
# (and workflow-runtime.js when the page has a <rapi-flow> with workflows)
unzip my-api-mcp.zip -d my-api-mcp
cd my-api-mcp
npm install

# One environment variable per security scheme (upper snake case); API_KEY is the fallback
API_KEY="your-key" npm start        # runs: tsx server.ts

# Point the server at another environment
API_BASE_URL="https://staging.example.com/v2" API_KEY="your-key" npm start
runtime.js

1. Executes any tool with fetch

createMcpRuntime(manifest, { fetch, credentials, baseUrls }) puts each argument where operation.params says, percent-encodes path parameters, applies the security scheme (API key in header, query or cookie; HTTP Basic; Bearer tokens for HTTP bearer, OAuth 2 and OpenID Connect) and returns the MCP result: pretty-printed content, structuredContent for JSON objects, isError with the HTTP status and body on 4xx and 5xx. The same file runs in Node, browsers and edge workers.

server.ts

2. A fixed stdio server

Built on @modelcontextprotocol/sdk, it reads manifest.json, registers its tools and resources and hands every call to the runtime. It is the same file for every API, so there is nothing API-specific to review. package.json depends on @modelcontextprotocol/sdk and tsx.

Environment

3. Credentials by security scheme

Each scheme reads an environment variable named after it in upper snake case (petstore_auth → PETSTORE_AUTH), with API_KEY as the fallback for any scheme. HTTP Basic takes user:password or <NAME>_USERNAME / <NAME>_PASSWORD. API_BASE_URL overrides the base URL. The Claude Desktop and Cursor config has one env entry per scheme.

Portable

4. Other languages read the same manifest

Only TypeScript is exported today. Because the manifest holds everything a server needs, a server in another language is a fixed file reading the same manifest.json, not another code generator.

Integration

Using MCP in Your Documentation

Enable the MCP generator in your documentation portal with a single attribute, or trigger it programmatically:

Declarative HTML Setup

index.html
<!-- 1. Enable the Export MCP Server button in the API overview -->
<script type="module" src="https://unpkg.com/rapidoc/dist/rapidoc-min.js"></script>

<rapi-doc
  spec-url="/specs/my-api.yaml"
  mcp="true"
  mcp-server-name="my-api-mcp"
  theme="slate"
> </rapi-doc>

Programmatic JavaScript API

app.js
// Access the RapiDoc custom element in JavaScript
const doc = document.querySelector('rapi-doc');

// 1. Build the manifest, the project files and the host configuration in memory
const result = doc.generateMcpServer({ serverName: 'my_api_mcp' });
console.log(result.manifest.tools);       // [{ name, description, inputSchema, annotations, operation }]
console.log(result.files['server.ts']);   // also manifest.json, runtime.js, package.json, README.md, ...
console.log(result.hostConfig);           // Claude Desktop / Cursor JSON config
console.log(result.budget);               // { totalTools: 5, estimatedTokens: 850, ... }

// 2. Download the project as <server-name>-mcp.zip
doc.downloadMcpServer();

// 3. Inspect token budget overhead before exporting
const budget = doc.getMcpBudget();
console.log(`Overhead: ~${budget.estimatedTokens} tokens/turn across ${budget.totalTools} tools`);

Ready to test the Curated MCP Generator?

Explore our live interactive demo with side-by-side token diagnostics, tuner options, and the exported manifest, server and runtime to inspect.