MCP Core Concepts Best Practices
Designing an MCP server well means every tool, resource, and prompt is predictable to the clients and models that use it, not just functional in your own testing.
This checklist gathers the practices worth following across tool schemas, resource URIs, prompt templates, and how you deploy the server itself.
How to Use This Checklist
- Walk through each lettered group when designing a new server, or when reviewing an existing one before sharing it more broadly.
- Treat the tool-design group as the highest priority, since most servers start there and most model-selection problems trace back to unclear tool schemas.
- Revisit the deployment group whenever a server moves from local prototype to something shared with a team or exposed remotely.
A - Tool Design
- Name tools with a verb first, describing the action clearly.
create_taskreads as an action;taskorhandlegives the model nothing to match intent against. - Write a one-line docstring plus an
Args:block for every tool. The docstring is the primary text the model uses to judge relevance, and per-argument notes clarify units and formats a type hint alone cannot express. - Keep each tool narrowly scoped to one job. A tool with a
modeoractionflag hides multiple behaviors behind one schema and makes model selection less reliable. - Use type hints,
Literal, or Pydantic models to validate inputs before your handler runs. This catches bad arguments as a schema error instead of a runtime crash inside your code. - Raise exceptions with specific messages on failure, never return
Noneor an empty string. A silent failure gives the model no signal that anything went wrong. - Avoid unguarded shared mutable state across concurrent tool calls. Race conditions on in-memory globals corrupt data under real usage; back shared state with a real datastore.
B - Resource Design
- Keep resources strictly read-only. Anything that creates, updates, or deletes data belongs in a tool, not a resource, so clients can safely cache or re-read resource content.
- Choose a clear, consistent URI scheme per entity type. A scheme like
articles://{id}ordocs://{filename}makes resources predictable to discover and reason about. - Guard any filesystem-backed resource against path traversal. Resolve the path and confirm it stays inside the intended root directory before reading, since a raw filename parameter can otherwise escape the served directory.
- Return structured content, like JSON, for anything with more than one field. Plain unstructured text forces every caller to write ad hoc parsing.
- Raise a clear exception when a requested resource genuinely does not exist. An empty return value hides the difference between "no data" and "an error occurred."
C - Prompt Design
- Reach for a Prompt only when wording consistency across multiple clients is the real problem. A prompt used by a single client adds design overhead without the benefit it exists for.
- Parameterize prompts with typed arguments instead of hardcoding varying data. This avoids duplicating a whole prompt function for every small variant.
- Lead multi-message prompts with explicit framing guidance before the user's request. A clear instruction message gives every caller a consistent structure to expect.
- Treat prompt wording as a maintained asset, not a write-once artifact. Because a prompt is centralized, an untuned prompt affects every client using it; iterate based on real output quality.
- Never hardcode sensitive or variable data directly into a prompt template's source. Pass it in as an argument at call time instead, the same discipline applied to any server-side code.
D - Primitive Selection
- Ask whether an operation changes anything before choosing a primitive. If yes, it is a Tool; if it is a pure read, consider a Resource first; if it packages reusable instructions, it is a Prompt.
- Avoid making everything a Tool out of convenience. Burying safe, cacheable reads inside the same primitive as destructive actions makes a server harder to reason about and easier to misuse.
- Pair Tools and Resources deliberately when a workflow needs both. A tool that creates a record and a resource that exposes it for later reading is a common, clean combination.
E - Transport and Deployment
- Default to stdio for local, single-developer tools. It needs no network configuration, TLS, or authentication, and its lifecycle is tied cleanly to the client that launched it.
- Move to HTTP/SSE only when a server genuinely needs to serve multiple clients or persist independently. Adding network complexity without that need is unnecessary overhead.
- Never bind a remote server to a public interface without authentication. Put it behind a reverse proxy with TLS and an auth layer, or restrict it to a private interface, before it is reachable beyond a trusted network.
- Consider an MCP tunnel when a server must stay inside a private network but still needs remote reachability. This can avoid the operational burden of opening inbound ports and standing up a public TLS endpoint, though it remains a research preview as of 2026, so verify current guidance before depending on it.
- Write handlers to be safe under concurrent calls once a server serves more than one client. A remote, shared server sees overlapping requests in a way a single-client stdio process never does.
Applying This Checklist in Order
- Start with A and B (1-2): tool and resource design decisions shape everything downstream, including how reliably the model selects the right capability.
- Add C only when needed: prompts are valuable, but skipping them for a server with a single client is a reasonable, deliberate choice.
- Finish with D and E before sharing broadly: primitive selection and deployment discipline matter most once more than one person or client depends on the server.
FAQs
Which group of this checklist matters most for a brand-new server?
Group A, tool design. Most first servers are built around tools, and most model-selection problems trace back to vague names, thin docstrings, or overly broad tool schemas.
Is it ever fine to skip the Prompt group entirely?
Yes. Many useful servers expose only tools and resources. Prompts are worth the design effort specifically when wording consistency across multiple clients is a real, observed need.
Why does this checklist separate resource design from tool design?
Because the failure modes are different: tool problems usually show up as the model calling the wrong thing or with bad arguments, while resource problems usually show up as data leakage, path traversal, or hidden side effects.
What is the single most common mistake this checklist tries to prevent?
Building every capability as a tool, including pure reads, which buries safe data lookups inside the same primitive as destructive actions and makes the server harder to reason about.
When should I revisit the deployment group (E) on an existing server?
Whenever the server moves from a local prototype toward being shared with a team or exposed remotely; the network and authentication concerns in that group only apply once a server leaves a single trusted machine.
Does using Pydantic models matter for every tool, or only complex ones?
Plain type hints are enough for simple scalar arguments. Reach for a Pydantic model when a tool needs field-level constraints, nested structures, or validation logic reused across multiple tools.
How strict should the path traversal guard on a resource be?
Strict enough that a resolved path is always checked against the intended root directory before any read happens, since a raw filename or path parameter should never be trusted directly from the caller.
Should I always prefer stdio unless I have a specific reason not to?
Yes. stdio is the simpler default with the least operational overhead. Move to HTTP/SSE (or a tunnel) only once you have a concrete need for multiple clients or independent persistence.
What is the risk of skipping authentication on a remote server because it's "internal only"?
Internal networks still have multiple users and services that should not all have equal access. Treat an internal HTTP/SSE server with the same authentication discipline as any other internal service.
Is centralizing prompt wording worth it for a server used by only one team?
Often yes, if more than one client application on that team invokes the same instruction. Centralizing still prevents drift even within a single team once more than one codebase depends on the wording.
How do I know if a tool has grown too broad and needs splitting?
If it accumulates many optional parameters to cover unrelated use cases, or gains a mode/action flag that branches into different behaviors, that is a strong signal to split it into narrower tools.
Related
- Understanding MCP: Tools, Resources, and Prompts - the conceptual foundation this checklist assumes.
- Defining Callable Tools in an MCP Server - the full detail behind the tool design group.
- Exposing Readable Data as MCP Resources - the full detail behind the resource design group.
- Building Reusable Prompt Templates with MCP Prompts - the full detail behind the prompt design group.
- Choosing Between stdio and HTTP/SSE Transports for MCP - the full detail behind the transport and deployment group.
- MCP Tunnels for Private Network Access Reference - the research-preview option referenced in the deployment group.
Stack versions: Written against the Claude model lineup current as of ~June 2026 - Claude Fable 5, Claude Opus 4.8, Claude Sonnet 5 (the default), and Claude Haiku 4.5 - and the current Model Context Protocol specification. Model names, SDK versions, and the MCP spec move quickly - verify current specifics at platform.claude.com/docs and modelcontextprotocol.io before relying on them.