The MCP Token Problem (and Three Ways to Solve It)
If you’ve built an MCP server, you’ve probably noticed something uncomfortable: your LLM burns through tokens before the user even types a question.
Most MCP servers register every tool upfront. When an LLM connects via tools/list, it receives the full catalog, every tool name, description, and JSON Schema input definition, injected straight into its context window. A hand-built Stripe MCP with 300 endpoints runs 50-80K tokens in the system prompt, before the conversation even starts.
That’s why developers are ditching MCPs for raw CLI wrappers and direct API calls. The problem is CLIs lose credential vaulting, access control, audit trails, and rate limiting, the stuff enterprises actually need. I didn’t want to pick one side of that trade, so I built mcp-gen, a CLI that takes any OpenAPI specification and generates a production-ready TypeScript MCP server. It gives you three ways to generate tools from the same spec, and which one you pick depends on how big the API is and how your users actually call it.
Generic mode: three tools, any API size
This is the one I think is interesting. Regardless of whether the API has 10 endpoints or 1,000, Generic mode registers exactly three tools:
| Tool | What it does |
|---|---|
{name}_list_schemas | Browse endpoints by category or search |
{name}_get_schema | Inspect one endpoint’s parameters |
{name}_api_call | Call any endpoint |
The LLM discovers progressively: browse, then inspect, then call. All endpoint metadata lives server-side in an endpoint_map.json and never gets sent to the LLM until it’s specifically requested. That keeps the tools/list response at roughly 800 tokens, always, whether it’s Petstore or the full Stripe API.
The cost is an extra round-trip. The LLM has to call list_schemas and get_schema before it can call the API. In practice this is mostly invisible for conversational use, since the LLM naturally asks “what can I do?” before doing anything anyway. I’d reach for Generic mode on any API with 50+ endpoints where token cost matters, which is most of them. I wouldn’t use it if you need single-shot tool calling with no discovery step, or if the API only has 5-10 endpoints where tokens were never going to be a problem.
Per-Endpoint mode: one tool per endpoint
This is the pattern most hand-built MCP servers already use. One tool per API endpoint, each with a typed input schema derived from the OpenAPI spec, with path, query, and header parameters flattened into a single object.
Each tool costs roughly 200-400 tokens. A 20-endpoint API runs about 5K tokens, which is fine. A 100-endpoint API hits 30K+, which isn’t. I’d use this mode for a small, focused API, under 30 endpoints, where every operation matters and you want the LLM to see the full surface area with zero discovery latency. Past 50 endpoints, or once most operations are rarely used, the token cost stops being worth it.
Curated mode: hand-picked plus a fallback
You pick 5-15 high-value endpoints as direct tools. Everything else stays reachable through a fallback api_call tool and an explorer for discovery.
curated:
tools:
- operation_id: createCustomer
- operation_id: getInvoice
- operation_id: createPaymentIntent
fallback:
enabled: true
explorer:
enabled: true
Ten curated tools plus fallback plus explorer costs about 2-4K tokens. This is the sweet spot once you actually know which operations matter most on a large API. If you don’t know that yet, start with Generic and curate later once usage tells you.
The numbers
Here’s how the modes compare at different API sizes:
These are estimates based on typical tool description and JSON Schema sizes, with “Hand-Built” assuming one tool per endpoint with verbose descriptions, which is the common pattern in open-source MCP servers. Generic mode’s cost stays flat no matter how big the API gets. Everything else scales linearly with endpoint count.
Unified parameter schema
There’s a second problem beyond token count: most MCP servers make the LLM understand HTTP routing.
// What most MCPs expect:
path: { petId: "123" }
query: { status: "available" }
headers: { "X-API-Key": "sk-..." }
That’s transport-level detail the LLM shouldn’t need to care about. Every token spent deciding “does this go in the path or the query string” is wasted. mcp-gen flattens all of it into a single params object:
// What mcp-gen expects:
params: { petId: "123", status: "available" }
// Routing handled server-side. Credentials injected from vault.
The server handles routing. Credentials are pulled from the vault at call time, so the LLM never sees auth headers at all.
Token efficiency is the headline, but every generated server also ships with credential vaulting (secrets pulled from the vault at runtime, never exposed to the LLM), audit logging on every tool invocation with customer context attached, multi-tenant support (customer ID from JWT, isolated credential scopes), Zod validation on every parameter before the API call, and a one-command deploy to Statio via pilum deploy, which gets you Cloud Run, gateway routing, rate limiting, and usage billing without extra setup.
There’s a speed benefit too. A full tool catalog sits in the context window on every turn, so the model reads through all of it again each time it decides what to do. With Generic mode, it pulls the one schema it needs, and that schema stays in the conversation. The next call to the same endpoint skips discovery entirely, because the model is working from what it already read instead of re-evaluating the whole catalog every turn.
The full technical breakdown with configuration examples is on the Statio mcp-gen page.
Resources
- mcp-gen technical deep-dive: full configuration reference and mode comparison
- Statio: managed MCP security platform
- MCP specification: the Model Context Protocol
- Romans.dev: SID Technologies product portfolio