MCP server design
Guidance for authoring a Model Context Protocol (MCP) server — the side that exposes context and capabilities to an LLM host. (Consuming MCP servers is covered elsewhere.) The model routes on the names, descriptions, and schemas you publish, so treat them as the public contract.
Pin to a dated spec revision. Default to the stable 2025-11-25 revision unless the host requires an older one. Negotiate the revision during initialization rather than hard-coding it. Treat any release-candidate or draft revision as a forecast and gate it behind explicit opt-in.
Choose the right primitive
A server offers three primitives. Pick by purpose, not convenience.
| Primitive | Control model | Use for |
|---|---|---|
| Resource | App/user-driven | Read-only context the host or user attaches (files, records, docs). No side effects. |
| Prompt | User-invoked | Templated, user-triggered workflows (slash commands, canned interactions). |
| Tool | Model-driven | Actions and side effects the model decides to invoke (queries, API calls, writes). |
- You SHOULD prefer a resource for anything read-only that the user or host selects as context.
- You SHOULD prefer a prompt for workflows a human explicitly invokes.
- You SHOULD model tools for operations the model chooses autonomously — and only those.
- Do not expose read-only context as a tool merely because tools are easier to wire up; that puts routing load on the model and invites accidental invocation.
Tool design
The host's model selects tools from their name and description alone, so these are load-bearing.
- Each tool MUST have a precise, action-oriented
nameand adescriptionthat states what it does, when to use it, and what it returns. - Tool names SHOULD be 1–128 chars, case-sensitive, and limited to
[A-Za-z0-9_.-](no spaces). Keep them unique within the server. - Use the optional
titlefor a human-readable display label; keepnamestable as the machine identifier. - Define a strict
inputSchema(JSON Schema, defaults to 2020-12). For zero-parameter tools, use{ "type": "object", "additionalProperties": false }. - Keep the tool surface small. Many overlapping tools degrade routing accuracy; consolidate or parameterize instead.
Structured output
- A tool SHOULD declare an
outputSchemaand return matchingstructuredContent. This gives the model and client typed, validatable results. - When
outputSchemais present, the server MUST returnstructuredContentconforming to it; clients SHOULD validate it. The schema root is restricted totype: "object". - For backward compatibility, also serialize the structured result as JSON in a
textcontent block. - Sanitize outputs and never leak secrets or internal identifiers the host should not see.
Error handling
The spec distinguishes two error channels — use both correctly (fail-fast at the right layer).
- Tool execution errors (API failure, validation, business-logic): return them in the result with
isError: trueand an actionable message. The model sees these and can self-correct. - Protocol errors (unknown tool, malformed request): return a JSON-RPC
error. The model usually cannot fix these. - Do not throw a protocol error for a recoverable tool failure — the model loses the signal it needs to retry.
Annotations (untrusted hints)
Set annotations to describe tool behavior so hosts can apply policy. All are hints with defaults; clients MUST treat them as untrusted unless the server is trusted.
| Annotation | Default | Meaning |
|---|---|---|
readOnlyHint |
false |
Tool does not modify its environment. |
destructiveHint |
true |
Updates may be destructive (only meaningful when not read-only). |
idempotentHint |
false |
Repeated calls with same args have no additional effect. |
openWorldHint |
true |
Interacts with an open external world (e.g. the web). |
- You SHOULD set these accurately; mislabeling a destructive tool as read-only invites unguarded invocation.
- Annotations inform UI and consent flows; they MUST NOT be your only safety control. Validate inputs, enforce access control, and rate-limit on the server.
Security baseline
- Servers MUST validate all tool inputs, enforce access controls, rate-limit invocations, and sanitize outputs.
- Assume a human-in-the-loop may gate invocations; write descriptions and errors that make consent decisions clear.