Contents
MCP delivers a tool’s natural-language description through the same trusted instruction context as the system prompt, which makes that description executable code with respect to agent behaviour — and makes MCP servers a software supply chain rather than a list of convenient endpoints. The controls that already govern application code apply here with almost no translation: a content-addressed manifest, a CI diff gate, an instruction-shape scanner, capability-scoped credentials, and egress allowlisting. Annotations are hints, never contracts.
Sources and scope: Primary sources are the MCP specification, revision 2025-11-25, the repository SECURITY.md, the MCP blog post on tool annotations, Microsoft’s developer security guidance, Invariant Labs’ tool-poisoning disclosure, Cloud Security Alliance research notes and the MCP threat taxonomy. This essay is desk research of the protocol specification, vendor security guidance and published incident reports — no hands-on testing was performed and no live MCP attack was executed.
MCP Tool Descriptions Are Executable Supply-Chain Assets
MCP sends tool descriptions through the same trusted instruction context as the system prompt, so a description is executable code for agent behaviour. The MCP spec explicitly calls descriptions untrusted unless obtained from a trusted server. Treating MCP servers as a supply chain is therefore architectural, not a single implementation bug.
The specification is explicit about where the boundary sits. The server/tools page states that descriptions “should be considered untrusted, unless obtained from a trusted server,” and that there “SHOULD always be a human in the loop with the ability to deny tool invocations.” Tools are model-controlled: the model discovers and invokes them from contextual understanding, not from a dispatch table. The repository’s SECURITY.md formalizes that posture as intended behaviour rather than a vulnerability — clients trust the servers they connect to, local servers are trusted like installed software, and users own server selection.
Consequences arrived quickly. Invariant Labs disclosed tool poisoning on 2025-04-01, affecting Anthropic and OpenAI integrations, Zapier-style workflow automation and the Cursor editor; a follow-up exfiltrated WhatsApp chat history through a harmless-sounding “random fact of the day” tool. The malicious text is invisible to the user, visible to the model, and effective against otherwise trusted servers.
Scale followed. MCPTox (August 2025) ran 20 prominent LLMs against 353 real tools from 45 live servers: mean attack success 36.5%, worst case 72.8%, and even the most cautious models refused in fewer than 3% of cases. Microsoft’s research classifies tool descriptions as supply-chain assets requiring production-code review rigour. CSA calls the weakness architectural: MCP mixes instructions and data in one channel, and every action remains technically authorized — “only unauthorized use of authorized access.”
A Hash-Pinned Tool Manifest in CI
Capture tools/list, canonicalize JSON by stable key order and dropping volatile fields, compute one SHA-256 per tool definition, and pin the set in mcp-tools.lock. Re-fetch in CI and fail on any hash delta. Review description diffs before updating the pin. One hash per tool catches poisoning, rug pull, name collision, and impersonation.
The pipeline has six steps and none of them are clever. Enumerate tools/list through the same client path production uses, following pagination to exhaustion. Canonicalize each definition — stable key order, normalized encoding, volatile wrapper fields dropped — so the manifest does not churn on whitespace. Hash one SHA-256 per tool definition, not one over the whole list. Pin server identity, transport, tool name and digest into mcp-tools.lock. Verify in CI by re-fetching and recomputing, failing the build on any delta. Review the description diff in the pull request before anyone updates the pin.
Per-tool hashing is what makes the four common variants legible. Poisoning is same-name, different-content and surfaces as a digest delta. A rug pull is that same delta after approval. Name collision or shadowing appears as two servers claiming one name. Impersonation appears as a familiar name with a new digest and a new server identity. A single hash over the whole list collapses all four into “the list changed.”
Dynamic lists are the complication. Servers declare capabilities.tools.listChanged, and tools/list paginates. A client that re-fetches every session and re-trusts what it finds has no gate at all — CSA’s description-poisoning note describes attackers skipping model compromise entirely and simply rewriting metadata between sessions. The pin converts that into a build failure, which is the code-review equivalent: a description change is a behavioural change.
Instruction-Shape Scanner as a Heuristic Tripwire
Scan every tool description for imperative verbs aimed at the agent, sequencing language such as before or after or first, references to other tools or servers, zero-width or Unicode obfuscation, base64 or hex blobs, excessive length, and near-duplicate tool names. This is a heuristic tripwire, not a proof.
The rules follow the MCP threat taxonomy’s detection indicators — unexpected behaviour changes, suspicious metadata modifications, duplicate or conflicting names, anomalous selection patterns, suspicious new registrations — reduced to things a linter can see. Imperative verbs addressed to the agent: “ignore,” “instead,” “you must,” “do not tell the user.” Sequencing vocabulary: before, after, first, then. References to other tools, servers, files or credential stores, which is the raw material of shadowing. Zero-width characters, bidirectional overrides and homoglyphs. Base64 or hex blobs long enough to be payloads. Length outliers against the corpus median, and names within a small edit distance of existing ones. Note that the taxonomy’s listed mitigations — cryptographic signing, metadata validation, behavioural monitoring, sandboxing, registry security — sit above scanning, not inside it.
False negatives matter more than the rules. Rephrased payloads that avoid the vocabulary pass. Two-step shadowing where each half looks benign passes. A terse malicious description passes while a verbose honest one trips. MCPTox is the reason to keep it anyway: it runs at build time, it is cheap, and it produces a reviewable artifact. Tripwire, not proof.
Capability-Scoped Credentials and Egress Allowlisting
A poisoned description cannot reach an attacker endpoint when tools receive least-agency credentials and network egress is allowlisted. Token passthrough is forbidden by the MCP spec, and SSRF protections must block private and reserved ranges, including cloud metadata. Consequential action classes require human-in-the-loop gating, and centralized tool-call telemetry flags volume, destination, and invocation-pattern anomalies.
Least agency per tool is the core idea: each credential authorizes the narrowest action set, scope and lifetime that tool needs. A calendar reader does not get a token that can send mail. The spec’s security best practices forbid token passthrough outright — servers “MUST NOT accept any tokens that were not explicitly issued for the MCP server.” Audience separation means a token minted for one resource is rejected by another, so a compromised server cannot replay a client token against an upstream API. It also closes the confused-deputy path, where a proxy with a static client ID, dynamic client registration and a consent cookie set before approval leaks an authorization code.
SSRF arrives through OAuth’s front door. During metadata discovery the malicious server controls the resource_metadata URL, the authorization_servers list and the token_endpoint. The documented mitigations are specific: enforce HTTPS and reject non-loopback http://; block private and reserved ranges per RFC 9728 §7.7 — 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, loopback, link-local 169.254.0.0/16 for cloud metadata, fc00::/7, fe80::/10; validate redirect targets hop by hop; route discovery through an egress proxy; and never hand-roll IP validation, because octal, hex and IPv4-mapped-IPv6 encodings defeat custom parsers. CVE-2025-6514 is the cautionary case: CVSS 9.6 command injection through OAuth discovery fields in mcp-remote 0.0.5 through 0.1.15, fixed in 0.1.16, in a package with more than 437,000 downloads and a place in several vendor integration guides.
Telemetry closes the loop. Because every individual action is authorized, monitoring must key on shape rather than permission: call volume against a per-tool baseline, destination novelty and first-seen domains, invocation patterns that contradict the tool’s declared purpose, and unexpected sequences across tools.
Annotation Tiering Is Preflight Only
Annotations are hints, never contracts. Use title, readOnlyHint, destructiveHint, idempotentHint, and openWorldHint only to shape a preflight confirmation decision, never as authorization control. DestructiveHint and openWorldHint default true. Hints from an untrusted server remain untrusted, so authorization must not depend on them.
ToolAnnotations shipped in the 2025-03-26 revision with five properties: title?, readOnlyHint? (default false), destructiveHint? (default true), idempotentHint? (default false), openWorldHint? (default true). The MCP blog post on annotations as risk vocabulary is unambiguous that each is a hint: annotations “are not guaranteed to faithfully describe tool behavior, and clients must treat them as untrusted unless they come from a trusted server.” Five independent SEPs for new annotations have been filed, so the vocabulary is still moving and client treatment should stay conservative.
Each hint has exactly one legitimate consumer. readOnlyHint shapes whether a confirmation dialog appears. destructiveHint defaulting true means an unannotated tool is treated as dangerous by the preflight layer. idempotentHint informs retry logic, not permission. openWorldHint flags that a tool can reach beyond a closed corpus, which is a useful trigger for egress review. title is display only.
The default-true posture on the destructive and open-world hints is right because annotations are claims made by the party under evaluation. Omission is not a safe claim; it is no claim, and absence should read as risk. The shortcut to avoid is letting readOnlyHint: true suppress an approval prompt — that converts a hint into an authorization control sourced from the untrusted side of the boundary.
Enforcement Point Tradeoffs
Compare enforcement at client-side pinning, server-side signed manifests, registry allowlisting, IDE config-review gates, and runtime monitoring. Each catches different failure modes: client pinning detects post-approval description changes; signed manifests establish provenance; registry allowlists limit supply; IDE review gates block auto-execution; runtime monitoring catches behavioural anomalies.
| Enforcement point | What it catches | What it cannot catch |
|---|---|---|
| Client-side hash pinning | Post-approval description, schema or tool-set changes; rug pulls; silent re-fetches per session | A malicious description shipped by a trusted server from day one; runtime behaviour of an approved tool |
| Server-side signed manifests | Provenance and integrity of the manifest from publisher to client; impersonation and typosquatting | Whether the client actually verified; compromised publisher keys; behaviour after approval |
| Registry / publisher allowlist | Supply broadening only through review; tool squatting and lookalike packages | A legitimate publisher whose description later changes; servers reached outside the registry |
| IDE config-review gate | Auto-execution of project-defined servers; one-keypress folder-trust paths | A user who approves without reading; headless CI paths that skip the dialog entirely |
| Runtime tool-call monitoring | Anomalous volume, destination and sequences; exfiltration attempts after the fact | Slow, low-volume exfiltration; the first call; anything before a baseline exists |
These layer because they fail differently. Client pinning is the only control that verifies what your client actually accepted, and the case for it is MCPoison, CVE-2025-54136, where Cursor bound approval to a server’s name rather than its contents: anyone with repository write access could commit an innocuous config, wait for approvals, then swap in a payload. Signing would not have caught that, because the approved name never changed. A digest would have.
Pinning is equally useless against a server that was malicious on first contact, which is what registry allowlisting and publisher provenance address. Neither helps against a server whose metadata is clean and whose responses carry the injection. That is why Microsoft’s guidance leans on making the boundary legible to the model through Prompt Shields, spotlighting, and delimiters and datamarking, rather than on validating metadata alone.
The IDE gate is where the sequencing went wrong. TrustFall found Claude Code, Cursor CLI, Gemini CLI and GitHub Copilot CLI all auto-executing project-defined MCP servers on folder-trust acceptance, all defaulting the prompt to affirmative, with Claude Code’s headless CI path — the default mode for the official claude-code-action — skipping the dialog entirely. The Amazon Q Developer flaws (CVE-2026-12957, CVE-2026-12958) repeated the shape via a crafted .amazonq/mcp.json loaded without consent, and the Miasma campaign planted adversarial MCP configuration across 73 GitHub repositories. Tool poisoning sits at entry number three on the OWASP MCP Top 10, and OWASP’s broader agentic threat work makes the same structural point: the gate arrives after execution.
Ops Runbook for a Hash Gate Fire
When the CI hash gate fires, quarantine the server, diff the description or schema, and block automatic re-approval. Distrust the server by rotating and revoking credentials issued to it, respecting token audience separation. Inventory whether your client auto-approves changed descriptions and disable that. Record the changed fields and compare them against instruction-shape scanner and telemetry anomalies before pin update.
- Quarantine. Remove the server from active client configs and from any auto-discovery path. Keep the old manifest entry; you need the previous digest for the diff.
- Diff. Compare pinned against fetched definitions field by field — description text, input schema, tool name, annotations. Record the delta in the incident ticket, not only in CI logs.
- Freeze approval. Block automatic re-approval and any “trust new version” toggle. A changed description is a new artifact and requires a new review.
- Revoke and rotate. Retire every credential ever issued to that server, relying on audience separation so tokens minted for other resources stay unaffected.
- Audit auto-approval paths. Check every client and CI job that loads MCP servers for re-approval of changed descriptions without a prompt, and disable it.
- Re-scan. Run the instruction-shape scanner over the new text and diff the flagged rule hits, not just the pass/fail verdict.
- Correlate telemetry. Pull tool-call records for the window between the metadata change and quarantine. Examine volume, destinations and cross-tool sequences.
- Decide. If legitimate, update the pin in a reviewed pull request with the description diff attached. If not, treat it as a compromised dependency: full rotation, downstream notification, and a check for other servers sharing the same publisher or credential.
FAQ
Three questions come up most often when teams start pinning tool metadata. Each answer below is deliberately short: the controls are complementary, none is sufficient alone, and the correct default is to assume a tool description is untrusted until a reviewed manifest says otherwise.
Does hash pinning prevent all tool poisoning?
No. It detects post-approval metadata changes; it cannot stop a trusted server publishing a malicious description from the start, nor prevent dangerous runtime behaviour. Pair it with instruction-shape scanning, egress allowlisting, and capability-scoped credentials.
Should I disable MCP tool auto-discovery?
In shared or untrusted repositories, yes. Auto-discovery plus default-affirmative trust dialogs is a one-step attacker execution path, as the TrustFall research and the Amazon Q disclosures showed. Require an explicit config-review gate before loading project-defined servers.
Are signed server manifests better than client-side pinning?
They solve different problems. Signed manifests establish provenance and integrity from publisher to client; client-side pinning verifies what your client actually accepted and detects drift. Use both where possible, but a hash pin is the minimum verifiable control.
📖 Related Reads
- Hermes Tutorials — Hermes Agent setup, configuration, and advanced workflows
- ToolBrain — tool reviews, LLM comparisons, and AI workflow guides
- NiteAgent — AI agent development, frameworks, and production patterns
- NoCode Insider — AI workflow automation with no-code tools, agents, and APIs
Cross-links automatically generated from CodeIntel Log.




