MCP server design patterns and anti-patterns

MCP server design patterns and anti-patterns

The specification tells you what a Model Context Protocol (MCP) server must do. It says much less about what holds up in practice, so here I’ve collected four recurring patterns that work and the anti-patterns behind most production problems, each with a pointer to the part of the series that explains the fix.

This is part 32 of my series on what an MCP server does under the 2026-07-28 specification, after part 31 on which AI hosts support which features. One frequently asked architecture question, whether to put an AI agent behind a tool, gets the next part to itself. The facts are as I read them in October 2026.

In brief

  1. Expose retrieval as two tools, a cheap search and a fetch by ID, and leave the reasoning to the calling model.
  2. Keep state across calls in explicit handles, not in the connection: per-connection state breaks behind load balancers, and a handle is checked against the caller on every call.
  3. When long work needs a confirmation, settle the inputs first and then return a task, with no side effects before the last input round.
  4. Ship one capability on several surfaces, from tools that work everywhere to an MCP App where a host can render one, each degrading to the next.
  5. Most production problems come from a short list of anti-patterns, among them one tool per endpoint, per-connection state, unprotected requestState and token passthrough.

Four patterns that work

Three of these are my advice rather than rules from the specification; the handle pattern follows the specification’s own guidance, as part 5 shows.

Search plus fetch

When your server fronts a body of content, expose retrieval as two tools: a cheap search that returns ranked, citable hits, and a fetch that returns a full item by its ID. The calling model does the reasoning, and your server does what it’s uniquely good at: knowing the index, the schema and the ranking.

  • search returns structuredContent with IDs, titles, scores, short snippets and source Uniform Resource Identifiers (URIs), plus a compact text rendering in content.
  • fetch returns the full document, or a resource_link the host can attach.
Note (Calling model): does the reasoning; Calling model → Your server: search; Your server → Calling model: ranked, citable hits IDs, titles, scores, snippets, source URIs; Calling model → Your server: fetch by ID; Your server → Calling model: the full document or a resource_linkYour serverCalling modeldoes the reasoningsearchranked, citable hitsIDs, titles, scores,snippets, source URIsfetch by IDthe full documentor a resource_link

The pattern also matches what two ChatGPT features, deep research and company knowledge, expect: OpenAI’s MCP guide asks servers that want to work with them to implement two read-only tools, search and fetch. The example instructions in part 4 tell the model to call search_docs first and then fetch_doc, which is this pattern in a sentence.

Explicit handles for state

There are no sessions under the 2026-07-28 specification, so state that spans calls needs a home of its own. Return an opaque handle from a creation tool, accept it on later calls, authorize the caller against it every time, state its lifetime in the description, and return a recoverable tool execution error when it expires. Part 5 explains why and builds one in Python, with an owner and an expiry.

Multi round-trip requests, then a task

For long-running work that needs a confirmation, resolve the inputs synchronously with Multi Round-Trip Requests (MRTR, part 13), then return a task handle and run the work asynchronously. Cause no side effects before the final input round. Part 26 shows how the two phases fit together.

Layered surfaces

Host support is uneven, as part 31 showed, so ship one capability on several surfaces, each degrading to the next. Here is one workflow, an incident postmortem, delivered five ways: tools that work everywhere, server instructions that point to the prompt and the skill, a prompt for integrated development environment (IDE) and chat hosts, a skill for hosts with Skills, and an MCP App for hosts with Apps.

Capability: incident postmortem → Tools get_incident, list_timeline_events works everywhere; Tools get_incident, list_timeline_events works everywhere → Server instructions for postmortems, use prompt postmortem or skill incident-postmortem; Server instructions for postmortems, use prompt postmortem or skill incident-postmortem → Prompt /postmortem incident_id IDE and chat hosts; Prompt /postmortem incident_id IDE and chat hosts → Skill skill://incident-postmortem hosts with Skills; Skill skill://incident-postmortem hosts with Skills → MCP App interactive timeline editor hosts with AppsCapability: incident postmortemToolsget_incident, list_timeline_eventsworks everywhereServer instructionsfor postmortems, use prompt postmortemor skill incident-postmortemPrompt/postmortem incident_idIDE and chat hostsSkillskill://incident-postmortemhosts with SkillsMCP Appinteractive timeline editorhosts with Apps

A host that supports only tools still gets the incident data through get_incident and list_timeline_events. Each layer it supports adds to that. Part 12 gives the reasoning behind each choice of primitive.

Anti-patterns that cause most production problems

I’ve grouped the twelve by where they live. Where the specification itself sets the fix, the group says so; the rest is my advice.

In tools and their results

Five concern the tools themselves and what they return. Two of the fixes are the specification’s: servers SHOULD return tools in a deterministic order, and under the MCP Apps extension, tools MUST return meaningful content even when a user interface (UI) is available.

Anti-patternWhy it hurtsInstead
Throwing exceptions on bad inputBecomes a protocol error, and the model can’t self-correctisError: true with an actionable message (part 9)
One tool per Representational State Transfer (REST) endpoint, 50 or more toolsContext bloat and poor tool selectionFewer, task-shaped tools with sharp descriptions (part 9)
Dumping whole documents into resultsHits host output limits and costs tokensSummaries plus resource_links (part 9)
UI-only answersUseless in text-only hostsMeaningful content always (part 25)
Unstable list orderingPrompt-cache misses, flaky paginationDeterministic sort (part 6)

One detail for Python: in the official software development kit (SDK), mcp 2.3.0, an unexpected exception in an MCPServer tool doesn’t become a protocol error (only a deliberate MCPError does). The SDK turns it into an isError result that says only Error executing tool <name>, so the model still can’t tell what to fix. Raise ToolError with a message that says what a valid value looks like, as part 9 shows.

In state and multi-step flows

Four concern state and multi-step flows, which have to work without sessions. One fix is the specification’s: if requestState influences authorization, resource access or business logic, the server MUST protect its integrity, for example with a Hash-based Message Authentication Code (HMAC) or Authenticated Encryption with Associated Data (AEAD).

Anti-patternWhy it hurtsInstead
Per-connection stateBreaks behind load balancers under the 2026-07-28 specificationExplicit handles in a shared store (part 5)
Side effects before input_requiredAbandoned retries leave half-done workNo writes until the last input round (part 14)
Unprotected requestStateTampering, replay, cross-user reuseAEAD or HMAC, plus the principal, an expiry and a digest of the request (part 14)
Elicitation-only flowsFail in unattended agentsA non-interactive path with clear errors (part 15)

In security and trust

The last three are about trust, and two of the fixes are the specification’s: a server MUST NOT accept or transit any token that isn’t valid for its own resources, and servers SHOULD NOT mark sensitive parameters with x-mcp-header, because intermediaries can see header values.

Anti-patternWhy it hurtsInstead
Token passthroughConfused deputy, audience violationsAudience-bound, delegated tokens (part 19)
Secrets in skills, instructions or x-mcp-headerLeaks to models, logs and intermediariesKeep secrets server-side (part 21)
Descriptions in the style of prompt injection, such as “always use this server”Looks like tool poisoning, so hosts and reviewers distrust itFactual, scoped descriptions (part 4)

Method and caveats

  • Built from the patterns and the anti-patterns of my guide’s chapter on design patterns, written against the 2026-07-28 specification; product facts as of October 2026.
  • The search plus fetch, task and layered-surface patterns are my advice; the handle pattern follows the specification’s guidance. Five anti-patterns break a rule of the specification or of its MCP Apps extension (unstable tool order, UI-only answers, unprotected requestState, token passthrough and secrets in x-mcp-header), and each group names its rules; the other seven are my advice.
  • Where OpenAI’s documentation is more precise than my notes (what ChatGPT expects search and fetch for), this article follows OpenAI’s MCP guide as checked on 8 October 2026.
  • Where the Python SDK differs from my notes (what an unexpected exception in a tool becomes), this article describes mcp 2.3.0, as I read its source on 9 October 2026.