Navigation
What We AreThe BrainPortfolioThe Lab's LabBuilt For YouThe WhiteboardServices & Prices
Let's Talk →
← The Whiteboard

What are the most common MCP server design mistakes?

Twenty-four products, twenty-four MCP servers, and the same six mistakes in nearly all of them. Tool count is the one everybody names and the one that matters least.

The most common MCP server design mistakes are shipping too many tools, writing tool descriptions as API documentation, exposing overlapping verbs the model has to choose between, returning whole records instead of answers, shipping write tools with no idempotency and no annotations, and assuming the host handles consent. Tool count gets all the attention. Description quality decides more runs.

Ross Jones — Founder, The Hopium Lab. Last modified 22 July 2026.

Twenty-four shipped products across fifteen sectors — pharma cold-chain, enterprise data governance, education, hiring, a developer-tools line — and every one ships an MCP server and a CLI, because everything speaks MCP. This is not a survey. These are the things I got wrong often enough to write rules about, checked against the specification, not other people's blog posts. AWS covered the bloat-and-confusion half well on 9 July 2026. Read that first; the writes and the consent boundary are here.

How many tools should one MCP server expose?

Fewer than the product has endpoints, and the count matters less than the bytes. Tool surface is the total bytes of names, descriptions and schemas a server puts into the model's context before the user has said anything. Anthropic's engineering write-up on advanced tool use puts numbers on it: 58 tools consuming roughly 55K tokens before the conversation starts, and internal cases reaching 134K tokens of definitions.

GitHub cut Copilot's built-in agent toolset from about 40 tools to 13 core tools plus four virtual categories on 19 November 2025. With the full toolset available they measured a 2–5 percentage point decrease in resolution rate on SWE-Lancer and SWE-bench Verified, plus 400ms of extra time-to-final-token. Their description of the failure is worth memorising: an oversized toolset makes agents "ignore explicit instructions, use tools incorrectly, and call unnecessary tools". Scope matters: Copilot's built-in tools, not the GitHub MCP server.

Deferred loading changes the arithmetic and not the design problem. Anthropic's Tool Search Tool reports an 85% token reduction with the full library still reachable, and MCP eval scores moving from 49% to 74% on Opus 4 and 79.5% to 88.1% on Opus 4.5. Client-side mitigation, though, and deferred loading finds the right tool by reading descriptions. Ship ninety tools and the bill lands on every host that connects.

Does tool count actually degrade selection accuracy?

Directionally yes, model-dependent, and the figure everyone quotes is thinner than its citation count suggests. RAG-MCP (arXiv:2505.03275, 6 May 2025) reports 43.13% tool selection accuracy with retrieval against 13.62% for putting every schema in the prompt. The method: qwen-max-0125, twenty web-search tasks per configuration, pools from 1 to 1,100 servers drawn from a registry of over 4,400. Directional evidence, one non-frontier model.

Counter-evidence is stronger than the search results admit. MCP-Bench (arXiv:2508.20453, NeurIPS 2025 workshop) found the curves "not strictly monotonic" — o3 and gpt-5 stay roughly stable from single- to multi-server settings while smaller models degrade clearly. Same paper: schema compliance and valid tool naming above 95% even mid-scale, so basic execution fidelity has stopped being the bottleneck.

Frontier models can count past thirty. The case for a small tool surface is context economics, semantic ambiguity and blast radius — never a claim that the model cannot cope.

Why is the tool description the real interface?

Because the description is the only prompt a server author gets to write, and most are written for a human reading reference docs. The measured effect is large: Anthropic reports tool use examples moving complex parameter handling from 72% to 90% accuracy.

The specification is blunter about what a description is than any vendor post. Revision 2025-11-25's key principles state that "descriptions of tool behavior such as annotations should be considered untrusted, unless obtained from a trusted server", and the tools page hardens the annotations half into a client MUST. Untrusted input, rendered into the model's context at registration, before the user's first turn.

A tool description is not documentation. It is the only prompt a server author gets to write, and the model reads it before it reads the user.

MCPTox (arXiv:2508.14925, 19 August 2025) measured the description surface across 45 live MCP servers and 353 authentic tools: 1,312 malicious cases, attack success up to 72.8% against o1-mini, and the best-refusing model declining under 3% of attacks. Writing a good description and writing a safe one are one task.

Why does returning the whole record break the run?

Because a tool result lands in context and stays there. The spec has carried the fix for revisions and almost nobody uses it: resource_link returns a URI the client can fetch or subscribe to instead of inlining the payload, outputSchema with structuredContent returns the answer's fields rather than the record's, and tools/list paginates.

Error design is the same context mistake pointing the other way. MCP separates protocol errors (JSON-RPC) from tool execution errors (isError: true), and only the second is built to be recoverable — execution errors "contain actionable feedback that language models can use to self-correct and retry with adjusted parameters". Return a protocol error where an execution error belongs and the model loses its route back.

Does MCP give you idempotency on write tools?

No. No key, no dedupe window, no replay semantics. idempotentHint exists in ToolAnnotations, and the schema's own note kills any comfort taken from it: all properties are hints, "not guaranteed to provide a faithful description of tool behavior".

Defaults are where the cost sits. In schema.ts for revision 2025-11-25, readOnlyHint defaults to false, destructiveHint to true, idempotentHint to false, openWorldHint to true. Ship a write tool with no annotations and every client is entitled to read it as destructive and non-idempotent — the most restrictive reading available, inferred from silence.

MCP has no idempotency mechanism. It has an advisory hint that defaults to false, on a field the schema itself calls a hint and not a guarantee.

Two jobs, both the server author's: implement real idempotency in the handler — caller-supplied key, atomic claim, dedupe window — and declare the hint honestly. The mechanism belongs to the deterministic shell around the model, never to the protocol.

Who owns consent, the host or the server?

Hosts own consent, and the specification admits it cannot enforce that. Hosts "must obtain explicit user consent before invoking any tool", and "while MCP itself cannot enforce these security principles at the protocol level, implementors SHOULD" follow them. A server author assuming the host handles it is relying on someone else's optional behaviour.

Precision earns its keep in the authorization spec. A protected MCP server is an OAuth 2.1 resource server, not an authorization server. OAuth 2.1 is an IETF draft (draft-ietf-oauth-v2-1-13) and not an RFC, so anyone citing an RFC number for it has not opened the page. Servers MUST implement RFC 9728 protected resource metadata, MUST validate that tokens were issued for them as the audience, and MUST NOT pass the client's token downstream. Clients MUST send the RFC 8707 resource parameter in both authorization and token requests. RFC 7591 dynamic client registration is now a last-resort fallback behind pre-registration and Client ID Metadata Documents. All of it binds HTTP transports — stdio servers SHOULD NOT use it and take credentials from the environment, so "MCP servers must do OAuth" is wrong for anything on stdio.

Which MCP server design mistake costs the most?

The write tool with no idempotency key. It costs nothing until the model is already mid-run and retrying, which is exactly why it survives the demo. Every row below has the same shape.

MistakeCost before the first user turnCost mid-runMechanism in revision 2025-11-25
Too many toolsWhole tool surface in context, every turnWrong tool chosen; explicit instructions ignoredtools/list pagination; fewer tools; client-side deferred loading
Description written as API docsNoneParameter errors, silent non-discoverydescription, title, worked examples, per-property descriptions
Overlapping verbsDuplicate schema bytesModel picks the near-twin and half-succeedsMerge behind one tool; from 2026-07-28, oneOf/anyOf
Whole-record returnsNoneContext spent on fields nobody readresource_link; outputSchema + structuredContent
Unannotated write toolNoneClient treats it as destructive and non-idempotent, or worse, does notToolAnnotations declared explicitly, plus a real key in the handler
Protocol error for a tool failureNoneModel cannot self-correct; run dies on a fixable mistakeisError: true with actionable text

A tool surface already in production, with nobody able to say which mistake is biting, is a two-day first look rather than a rebuild.

What changes in the 2026-07-28 revision?

Revision 2026-07-28 publishes in six days and removes things posts still describe as current. The release candidate, locked 21 May 2026, removes the initialize/initialized handshake and the Mcp-Session-Id header outright, so "any MCP request can land on any server instance". Tool inputSchema gains full JSON Schema 2020-12 composition — oneOf, anyOf, allOf, conditionals, references — while keeping its object root, and outputSchema becomes unrestricted. Tasks demote from experimental-in-core to an extension. Clients must validate the iss parameter per RFC 9207.

The schema change cuts against standard advice, mine included. oneOf, anyOf and allOf make a discriminated union inside that object root newly viable, so a pair worth splitting today is one tool with two branches next week. Judgement, not a rule: split when the paths need different descriptions, keep one tool when they need different fields.

How do MCP servers fail in production?

In eight recognisable ways — the six mistakes above, plus two that only show up once a server is public. Unnamed failure modes never get budget.

  • The kitchen-sink server. One tool per endpoint, because the endpoints already existed. Nobody ever justified a tool.
  • The API-doc description. Written for a developer with the reference open: what the tool takes, never when not to call it.
  • The near-twin pair. search_orders and find_orders: distinguishable by a human reading both, not by a model reading one.
  • The dump-and-pray return. Full record inlined on every call, and the run dies four calls later.
  • The unannotated write. No annotations, so destructiveHint defaults to true and idempotentHint to false, and client caution becomes guesswork.
  • The protocol-error swallow. A JSON-RPC error where isError: true belonged, stripping the actionable feedback the model needs to retry.
  • The rug pull. A benign description approved at onboarding, swapped later via notifications/tools/list_changed, with nothing requiring consent again. Named by Invariant Labs, unpatched by design.
  • The passthrough token. The client's token forwarded to a downstream API, which the authorization spec forbids in as many words.

What should an MCP tool-design checklist contain?

Five groups: schema, description, surface, returns and writes, auth and consent. The version below runs against my own servers, revised 22 July 2026. Copy it, fork it, argue with it.

MCP TOOL DESIGN CHECKLIST — v1.0 (22 July 2026)
The Hopium Lab. Written against spec revision 2025-11-25.
Re-check every line after 2026-07-28.

SCHEMA
[ ] Name 1-128 chars, [A-Za-z0-9._-] only, unique within the server
[ ] inputSchema is a valid JSON Schema object, never null
[ ] Parameterless tool: {"type":"object","additionalProperties":false}
[ ] Every property described; enums list their valid values
[ ] outputSchema declared, and structuredContent conforms to it

DESCRIPTION (a prompt, not documentation)
[ ] States when NOT to call the tool, and what to call instead
[ ] Names the nearest neighbouring tool, to break the tie
[ ] Carries one worked call example with real parameter values
[ ] Reviewed as untrusted input, because that is what the spec calls it

SURFACE
[ ] Tool count justified per tool, never per endpoint
[ ] tools/list paginates (cursor / nextCursor)
[ ] No two tools answer the same user question
[ ] listChanged declared only if notifications/tools/list_changed is sent

RETURNS AND WRITES
[ ] Default response is the answer's fields, not the whole record
[ ] Large or binary payloads returned as resource_link, never inlined
[ ] Failures return isError: true with text the model can act on
[ ] JSON-RPC errors reserved for protocol faults the model cannot fix
[ ] Annotations declared explicitly — silence reads as destructive and
    non-idempotent, because those are the schema defaults
[ ] Handler implements real idempotency: caller key, atomic claim,
    dedupe window. idempotentHint labels that work, never replaces it

AUTH AND CONSENT (HTTP transports; stdio takes credentials from env)
[ ] RFC 9728 protected resource metadata served
[ ] Tokens validated for this server as the intended audience
[ ] No token passthrough downstream — the spec says MUST NOT
[ ] Descriptions pinned by hash, so a change is a consent event

Every line is an afternoon's work with libraries that already exist. Tool count is the mistake everyone writes about because it is visible from outside. The expensive ones are the write with no key and the description nobody read as a prompt. Both only surface in a trace.

Ross Jones, Founder, The Hopium Lab. Last modified 22 July 2026. Engineering commentary, not legal advice.