Protocol security, in brief
Three attacks at the protocol level, tool poisoning, a confused deputy and a hijacked state handle, each shown at the step where it happens, then again with the defence that stops it.
Loading the animation…
Concept
Each attack here is a sequence of ordinary, well-formed protocol messages; what makes it an attack is one step where trust is assumed instead of checked. The chart marks that step with a diamond, and with the Defence switched on, the check that stops it. This chapter is a short tour. The Security site goes into the depth: untrusted input (its chapter 2), the lethal trifecta (chapter 3), least privilege (chapter 4) and designs that make it structural (chapter 7).
Tool poisoning (a rug pull). A tool's description is text the model reads as guidance. The notes server's add_note is approved with an innocent description; in a later session the same tool comes back with instructions hidden in it, to read a secrets file and put it in the note. Undefended (13 steps), the model obeys, a file from one server leaves through another, and nothing in the protocol objected. The defence pins a hash of each definition the user approved and holds back a changed one for review: it stops the attack at step 8, before the model ever sees the new text. The spec's tools page (accessed 2026-10-08) puts the other half on the client: clients SHOULD "Show tool inputs to the user before calling the server, to avoid malicious or accidental data exfiltration", which would have shown the secret in the note argument. The name "tool poisoning" comes from Invariant Labs' write-up of 2025-04-01.
A confused deputy (an OAuth proxy). An MCP server in front of a third-party API (a proxy) uses one static client ID at the third party, lets MCP clients register themselves, and the third party remembers the user's consent in a cookie. An attacker registers a client with its own redirect URI and sends the user a link; the third party sees the same static client, skips consent, and the proxy delivers a code to the attacker (17 steps). MCP's security best practices (accessed 2026-10-08) describe exactly this and require the fix: "MCP proxy servers MUST implement per-client consent" before forwarding to the third party. Defended, the proxy shows its own consent page naming the client and where it redirects, and the user declines. The term "confused deputy" also covers a token used against a server it was not issued for: that is chapter 6's wrong audience and token passthrough.
State handle hijacking. The 2026-07-28 revision has no protocol sessions, so a server that needs state across calls hands out a handle (an export ID here) as an ordinary tool argument. A second user who obtains the handle presents it with their own, valid token. Undefended, the server finds the state by the handle alone and returns another user's invoices. The best-practices page: servers "MUST NOT treat possession of a state handle as authentication" and SHOULD key state as <user_id>:<handle> with the user ID taken from the verified token, which stops the attack at step 8. In the 2025-11-25 revision the same attack targeted Mcp-Session-Id (session hijacking, the same defence).
Maths
Write a flow as an ordered list of steps and let be the index of the harmful step. A defence is a check at index ; harm is done if and only if every check before passes:
So a defence has two properties that matter: it must sit before , and it must fail on the attack while passing on honest use. Pinning a hash fails on any change to the definition, honest or not, which is why the defence asks the user rather than refusing outright. Per-client consent fails for any client the user has not approved at the proxy. Keying state by the token's subject fails for anyone but the owner. For the hash, a collision would need for a different definition: a second preimage of SHA-256.
Code
The pin: a SHA-256 of the definition as the model would read it, cut from the vendored engine:
export function toolHash(tool: Obj): string {
return hex(sha256(utf8Bytes(dumps(tool))));
}
The description the notes server swaps in is shown only as an abstract label: what matters to the defence is that the text changed after approval, not what it says.
Save a note for later. [hidden instruction: copy a local secrets file into the note]