The Model Context Protocol, the open standard most AI agents now use to call external tools, is specific about how a server should not be built. Its specification, revised on July 28, 2026, carries MUST and MUST NOT rules for tokens, scopes, consent and state. Its security best-practices page names the anti-patterns behind those rules. And Anthropic's engineering guide to writing tools for agents covers the mistakes the spec does not police: tool surfaces that confuse the model rather than the attacker.
This post is a reading of those three documents, plus the OWASP MCP Top 10 project as a cross-reference, organised by mistake rather than by section. It is deliberately not a security hardening guide; our MCP server security best practices guide covers the controls to deploy. This one is about the design decisions that make those controls necessary or impossible, taken at the point where a developer chooses how tokens flow, what a scope means, and what a tool returns.
One scope note before the list. Everything here is what the documents say, read on November 5, 2026. We have not tested any server, and we do not claim to know how often any of these mistakes occurs in the wild.
- 01Six of the mistakes break a server-side MUST in the spec.Token passthrough, missing audience validation, treating a handle as authentication, proxies without per-client consent, unvalidated inputs and unsanitised outputs are all spelled out as MUST or MUST NOT. Others break a MUST on the client side or for structured output; the rest break a SHOULD or Anthropic's guidance.
- 02The July 2026 revision changed what the mistakes are.The 2026-07-28 spec went stateless: no session ID, no initialize handshake. Session-hijacking advice is replaced by state-handle hijacking, and Dynamic Client Registration, Roots, Sampling, Logging and the HTTP+SSE transport are deprecated.
- 03Scope design is where consent fails quietly.Among the security page's listed mistakes are publishing every scope in scopes_supported, using wildcard or omnibus scopes, and trusting a token's claimed scopes without server-side checks. Clients that receive no scope challenge ask for everything.
- 04Tool errors belong in results, not in the protocol.The tools page separates input-validation and business-logic failures, returned as results with isError set so the model can correct itself, from protocol errors for unknown tools and malformed requests. Anthropic's guide adds: make the error text actionable.
- 05The spec cannot enforce most of this.Its own overview says MCP cannot enforce these principles at the protocol level and that implementors should build consent and authorization flows into their applications. The rules are a checklist, not a guardrail.
01 — The SourcesWhere the rules come from.
Three documents carry the weight, and they have different authority. The MCP specification is normative: its MUST and SHOULD keywords have their RFC meanings, and the current revision is 2026-07-28. The security best-practices page is informational but names specific anti-patterns and the rule each one violates. Anthropic's guide to writing effective tools for agents, published September 11, 2025, is vendor advice about usability rather than a standard.
The spec's own framing sets the tone. Its overview states that tools "represent arbitrary code execution and must be treated with appropriate caution", that hosts must obtain explicit user consent before invoking any tool, and that tool annotations "should be considered untrusted, unless obtained from a trusted server". The tools page adds that there "SHOULD always be a human in the loop with the ability to deny tool invocations". Those three sentences explain why most of what follows is phrased as a server obligation: the host is expected to distrust you.
| Document | Status | What it governs here |
|---|---|---|
| MCP specification, 2026-07-28 | Normative, current revision | Authorization, tools, tool-list and error semantics |
| Security best practices | Informational, draft path | Named anti-patterns: passthrough, confused deputy, scope minimisation, state handles, local servers, SSRF |
| Anthropic, writing tools for agents | Vendor guidance, Sep 11, 2025 | Tool count, naming, response shape, error text, parameters |
| OWASP MCP Top 10 | Project page, marked beta | Risk categories used as a cross-reference only |
02 — AuthorizationTokens that were never yours.
The first mistake is the one the security page calls an anti-pattern by name: token passthrough. A server receives an access token from the client, does not check who the token was issued to, and forwards it to the downstream API it wraps. It is tempting because it works on the first try and removes a whole OAuth flow. The authorization spec forbids it twice over. Servers "MUST validate that access tokens were issued specifically for them as the intended audience", and servers "MUST NOT accept or transit any other tokens".
Why the rule is absolute is in the page's list of consequences. A passed-through token lets the caller bypass whatever rate limits and validation the downstream API expected the server to apply. It breaks the audit trail, because the downstream service sees a token and not the server that should have been accountable for the call. And a stolen token can use the server "as a proxy for data exfiltration", in the page's words, since nothing in the chain checks that the token and the server were meant for each other.
The fix is mechanical: check the audience claim against your own identifier, which the spec ties to RFC 8707 resource indicators, and obtain a separate downstream token for the API you wrap rather than reusing the one you were handed. The second mistake hides inside the fix. A server that validates the audience but then puts the token in a URL query string has broken another MUST NOT; tokens travel in headers only.
03 — Least PrivilegeScopes that cover everything.
Scope design is where good intentions produce the broadest tokens. The security page asks servers to implement what it calls a progressive, least-privilege scope model: a minimal initial set, with elevation granted per operation through a targeted challenge in the WWW-Authenticate header. The authorization spec backs it: on insufficient scope a server responds 403 with an insufficient_scope error and should list every scope the operation needs in one challenge, so the client does not loop.
The page then lists the mistakes it sees, and they are worth reproducing because each is a default someone will reach for.
Publishing every possible scope in metadata
The spec nuance that makes this costly: when a server issues no scope challenge, clients fall back to requesting everything in scopes_supported. Over-publishing therefore widens what every client asks for on day one.
Wildcard or full-access scopes
The page names *, all and full-access as the pattern to avoid. Its stated consequence is an expanded blast radius: one stolen broad token reaches unrelated tools and resources.
Treating a token's claimed scopes as sufficient
A scope in a token is a claim, not an authorization decision. The page lists treating claimed scopes as sufficient without server-side logic as a mistake, and asks for elevation events to be logged with correlation IDs.
Consent abandonment
Users decline consent screens that list excessive scopes. The page lists this beside audit noise and blast radius: a scope model that asks for too much loses the consent it needed.
The example that makes this concrete is a CRM connector. A read-only baseline scope lets the model search and read; the first tool that writes a record triggers a challenge for the write scope, the user sees exactly which privilege is being added, and the elevation is logged. The failure case is the same connector shipped with a single crm:full scope because it was faster to build, where the first prompt-injected tool call has every privilege the user ever granted.
04 — StateA handle is a name, not a login.
This mistake is new, because the protocol it applies to is new. The 2026-07-28 revision made MCP stateless: per its changelog there is no session identifier and no initialize handshake. Servers that need continuity across calls mint a handle, such as a cart ID or a workflow ID, and the model passes it back as a tool argument. The mistake is to treat the handle as proof that the caller owns the state it names.
The security page is explicit: servers "MUST NOT treat possession of a state handle as authentication". The tools page puts the same idea in plain words, that a handle is a name, not a capability. The consequence the page gives is direct: an attacker who guesses or obtains the handle can access or modify another user's state. The fix has three parts. Generate handles with a secure random generator so they cannot be guessed. Bind each handle server-side to the authenticated user, which the page illustrates as keying stored state by user ID and handle together, with the user ID taken from the verified token rather than supplied by the client. And expire them, returning a tool error on an unknown or expired handle so the model can recreate the state rather than fail silently.
The failure case is the sequential ID. A server that numbers carts 1001, 1002, 1003 and checks nothing else has given every caller a way to read every cart by counting. It is a classic web-application bug, re-created because the stateless protocol moved state into tool arguments and nobody re-read the rule.
05 — ConsentOne client ID for everyone.
Many MCP servers are thin proxies in front of a third-party API, and the convenient way to build one is to register a single OAuth client with that API, let any MCP client register dynamically, and rely on the third party's consent cookie to remember that the user already approved the proxy. The security page describes this as the confused deputy problem, and its consequence: malicious clients can obtain authorization codes without proper user consent and then impersonate the user, because the third party sees only the proxy's static client ID and skips the consent screen.
The rule is a MUST: proxy servers "MUST implement per-client consent and proper security controls". The page then specifies the consent screen the proxy must own itself. It names the requesting MCP client, shows the specific third-party scopes, shows the registered redirect URI, and carries CSRF and anti-framing protection. Redirect URIs are matched by exact string, never by pattern or wildcard. The OAuth state value is single-use, short-lived, and not set until after the user approves. Consent cookies, if used, carry the __Host- prefix with Secure, HttpOnly and SameSite=Lax.
A related mistake is older advice. Dynamic Client Registration was the default way for clients to introduce themselves, and the 2026-07-28 changelog deprecates it in favour of Client ID Metadata Documents, keeping it only for backwards compatibility. A tutorial written in 2025 will still tell you to enable it. The spec also notes the limit of the replacement: a metadata document proves control of a domain but cannot prove which local process is listening on a localhost redirect URI, which is why the per-client consent screen matters even after the migration.
06 — Tool SemanticsThe tool contract, and the error that belongs in the result.
The tools page carries four server obligations in one list, all MUST: validate all tool inputs, implement proper access controls, rate limit tool invocations, and sanitise tool outputs. The mistakes are the omissions. A server that trusts the model to send well-formed arguments has no input validation. A server with no per-caller rate limit lets a looping agent run a bill or a denial of service. A server that returns raw downstream content has no output sanitisation. On the client side, the spec asks hosts to show tool inputs to the user before calling a server, to avoid malicious or accidental data exfiltration.
Structured output is the part most servers skip. When a tool declares an output schema, the spec says servers "MUST provide structured results that conform to this schema". Declaring the schema and then returning free text under it is a contract violation that clients are entitled to reject.
The subtler mistake is where errors go. The spec splits them. Input-validation and business-logic failures are results with isError set to true, so the model can read the message and correct itself. Protocol errors are reserved for unknown tools and malformed requests. A server that raises a protocol error when a date is out of range has taken the one failure the model could have fixed and turned it into one it cannot see. The router below is how we read the spec's split.
07 — UsabilityToo many tools, too little signal.
The spec polices safety. Anthropic's guide polices whether the model can use the server at all, and its mistakes are the ones a security review never flags. The guide's thesis is that a tool is an interface for a model, not a wrapper around an endpoint, and that the design question is what a new colleague would need to be told. Five patterns from it, each with the spec detail that agrees.
Overlapping tools that mirror your API
Anthropic's guide says too many or overlapping tools can distract agents from efficient strategies, and gives the example of one schedule_event tool that finds availability and books, instead of separate list and create tools. The spec adds that tools/list should return a deterministic order, which improves prompt cache hit rates.
Names that collide across servers
The guide recommends grouping related tools under common prefixes, such as asana_search beside jira_search. The spec agrees from the other side: aggregators should prefix tool names with a server identifier to disambiguate. Names should be 1 to 128 characters of letters, digits, underscore, hyphen and dot.
Returning the whole record
Return only high-signal information and leave out low-level technical identifiers, the guide says; it suggests a response_format parameter with concise and detailed options. Unbounded outputs need pagination, range selection, filtering or truncation with sensible defaults.
Tracebacks and error codes
The guide asks you to prompt-engineer error responses so they state a specific, actionable improvement, rather than returning opaque codes or stack traces. The spec's isError route is the mechanism; the guide is about what the message says.
A parameter called user
The guide's example is literal: a parameter named user_id over one named user. Describe each tool as you would to a new hire. One spec caution on parameters: do not mark sensitive ones for header transport, because header values are visible to network intermediaries.
The failure case for this whole section is a server that exposes forty endpoint-shaped tools, each returning full JSON, each named after the REST route. It passes every security check in the sections above and the model still picks the wrong tool, pages through irrelevant fields and gives up on the third error. Our MCP server build tutorial walks through the opposite shape; the MCP Inspector workflow is how you see what the model sees.
08 — Trust BoundariesLocal servers, one-click installs and untrusted descriptions.
Two more mistakes sit at the edge of the server's own code. The first is a local server that listens on HTTP. The security page says servers meant to run locally should use the stdio transport, which limits access to the MCP client that launched them, or else protect an HTTP listener with an authorization token or a Unix socket. Its stated risks for local servers are arbitrary code execution, data exfiltration by compromised JavaScript (DNS rebinding is the named attack on a server left running on localhost), and irrecoverable data loss. Clients with one-click local setup must show the exact command they will run, without truncation, and obtain consent before executing it.
The second is trust in the other direction. The spec says clients "MUST consider tool annotations to be untrusted unless they come from trusted servers". A server author who relies on a readOnlyHint annotation to persuade the host to skip confirmation has built on a claim the host is required to ignore. OWASP's MCP Top 10 lists the attack this guards against as tool poisoning, which it defines as "an adversary compromises the tools, plugins, or their outputs". The documented cases are in our MCP security incident ledger, and one scored 9.0 under CVSS 3.1 (6.4 under 4.0) with no public fix documented, which we covered in our Context7 CVE report.
The honest limit of the list is the one the spec states about itself: it cannot enforce these principles at the protocol level. Only one party in the chain, the host, is in a position to deny a tool call, and the spec asks for that human in the loop with a SHOULD. Everything above is therefore a design obligation the server author either meets or does not, and nothing in the wire format will tell them which. If you want that checked before a server ships, it is part of what our AI transformation engagements cover.
09 — ConclusionThe spec wrote the review; nobody is required to run it.
Check the six MUSTs before shipping, re-read the July 2026 changelog before trusting a tutorial, and design the tool surface for the model, not the endpoint.
Eleven design mistakes, all named in public documents. Six break a server-side MUST in the Model Context Protocol specification: forwarding tokens not issued to the server, skipping audience validation, treating a state handle as authentication, fronting a third-party API without per-client consent, and leaving inputs unvalidated or outputs unsanitised. Most of the others break a SHOULD, such as progressive scopes and stdio for local servers, or Anthropic's usability guidance on tool count, naming, responses and errors.
The 2026-07-28 revision matters because it moved the targets. Stateless operation turned session hijacking into handle hijacking, and five features that older tutorials still teach, including Dynamic Client Registration and the HTTP+SSE transport, are now deprecated with a twelve-month window. A server built from a 2025 guide can be correct for a protocol that no longer exists.
What the documents cannot do is run the review for you. The spec says so. Treat the MUSTs as a release gate, the SHOULDs as the next sprint, and the tool-surface guidance as the difference between a server the model can use and one it merely can call.