MCP's destructive_hint Is Not a Security Boundary, and LangChain v1.4.0 Just Made It Easier to Forget
Tool annotations are a claim the server makes about itself. Here is where the real gate goes.
There is a boolean in the Model Context Protocol called destructiveHint, and its default value is true. Not false. The spec authors decided that a tool arriving with no annotations at all should be assumed capable of destroying something, on the theory that pessimism is cheaper than trust. That default is the most honest thing in the whole annotation system, and it is also the thing almost nobody preserves once they start writing approval logic.
On September 1, LangChain shipped langchain v1.4.0 and moved MCP support into the core package under langchain.mcp. One of the headline features is that every MCP tool now carries its provenance in an mcp metadata namespace, including annotations like destructive_hint, which LangChain's own changelog describes as the mechanism for gating destructive tools behind approval. That is a real ergonomic improvement. It is also about four lines of code away from a security model that does not work.
The gap between a hint and a contract
The MCP maintainers wrote a whole post about this in March, and it is the most useful thing published about tool annotations so far. Tool Annotations as Risk Vocabulary, by maintainer Ola Hungerford with Sam Morrow of GitHub and Luca Chang of AWS, lays out the whole interface. Annotations shipped in the 2025-03-26 spec revision and there are only four booleans plus a display title:
interface ToolAnnotations {
title?: string;
readOnlyHint?: boolean; // default: false
destructiveHint?: boolean; // default: true
idempotentHint?: boolean; // default: false
openWorldHint?: boolean; // default: true
}
Every property is a hint. The post is blunt about what that means: annotations are not guaranteed to faithfully describe tool behavior, and clients must treat them as untrusted unless they come from a trusted server. Under the section titled "What Annotations Can't Do," the maintainers put it plainly. An untrusted server can claim readOnlyHint: true and delete your files anyway.
This was a known problem before the feature shipped. During review of the original proposal, MCP co-creator Justin Spahr-Summers asked the question that has never been fully answered: the information would be very useful if it could be trusted, but how does a client make use of a flag knowing that it is not trustable? Basil Hosmer pushed further and argued clients should ignore annotations from untrusted servers entirely, even title. The compromise the spec landed on was to call everything a hint, require clients to treat hints as untrusted by default, and leave the weighting to each client.
So the annotation is a self-report. You are asking the party you intend to constrain whether it needs constraining.
Why this is worse than it sounds
If MCP servers were a small set of vendor-audited endpoints, a self-report would be tolerable. They are not. A working agent session routinely combines tools from several servers, some of them installed off the internet twenty minutes ago, and the risk is a property of the combination rather than of any single tool.
The maintainers connect this to Simon Willison's lethal trifecta: access to private data, exposure to untrusted content, and the ability to communicate externally. Any agent holding all three is one injected instruction away from exfiltration, because models cannot reliably separate a user's instructions from instructions an attacker embedded in a calendar invite or a web page. Researchers have already demonstrated the chain using a malicious Google Calendar event description, an MCP calendar server, and a local code execution tool.
search_emails is not safe or dangerous on its own. It depends entirely on what else the agent can reach in the same session. No per-tool annotation can tell you that, and there is currently no annotation for "this tool sees untrusted content" at all. Five separate proposals are open trying to fill that gap: trust and sensitivity annotations co-authored by GitHub and OpenAI, a governance and UX annotation set, plus unsafeOutputHint, secretHint and trustedHint. A Tool Annotations Interest Group with participants from Microsoft, OpenAI, AWS, Cloudflare and Anthropic is forming to work through them. None of that is shipped.
Meanwhile the adoption picture is thin in the other direction too. The maintainers note that no MCP client currently lets users filter tools by annotation values, and none surface annotations as context inside an approval prompt. The closest thing to a production analog is GitHub's read-only mode, which about 17% of users enable.
Put this into practice
The good news is that LangChain v1.4.0 shipped the deterministic half of this in the same release, and it is the half worth building on.
Start with elicitation interrupts, not annotations. In v1.4.0, when an MCP server asks for input mid-call, MCPAdapter surfaces the question as a LangGraph interrupt(). A person answers and the run resumes. That is a real control flow gate that does not depend on anything the server claimed about itself in advance. If you already run LangGraph, this is the cheapest correct thing you can add this week:
pip install "langchain[mcp]"
from langchain.mcp import MCPAdapter
adapter = MCPAdapter("https://your-server.example/mcp")
tools = await adapter.list_tools()
Use annotations for the interface, not the decision. Show destructive_hint in your approval dialog. Sort by it. Warn on it. Let a user see what the server said about itself before they click. That is the use the maintainers explicitly endorse, and it costs you nothing if the server lied, because a human is still in the loop.
Keep an allowlist that you own. If your policy is "these six tools may run unattended and everything else stops for approval," write those six names down in your own config. A server that renames a tool or flips its own hint should not be able to silently promote itself into your auto-approve path. Cross-reference the annotation against your list rather than replacing your list with the annotation.
Treat annotations as one input to a policy engine, never the whole engine. The useful pattern the maintainers describe is a rule like "open-world tools are blocked in any session that has already touched private data." That rule works because the engine tracks session state, and the annotation is just one signal feeding it. Build the session taint tracking first. The hints become useful the moment you have somewhere to put them.
Put the hard guarantees in the network. If you need certainty that a tool cannot exfiltrate data, that is a job for egress controls or a sandbox. The March post says this directly and cites the same reasoning the maintainers applied to server instructions: do not rely on soft signals for things that need to be hard guarantees.
If you author a server, annotate honestly anyway. Set readOnlyHint: true on read-only tools, destructiveHint: false on additive operations, openWorldHint: false on closed-domain tools. Your annotations will be treated as untrusted by careful clients and that is fine. They still improve the experience for every user whose client leans on them for UX, which is most of them.
Honest limitations
I am arguing against a feature I think should exist, so let me be specific about where this argument is weak.
The first limitation is that annotation-based gating is meaningfully better than nothing for the most common real deployment, which is an enterprise running its own internal MCP servers behind its own auth. In that setup the server author and the client operator are the same organization, the trust relationship is real, and the hint is close enough to a contract to act on. The maintainers call this graduated trust and say it is more of a design opportunity than a shipped feature, because most clients still treat installation itself as the only trust signal and do not distinguish further. If you control both ends, gating on destructive_hint is a reasonable engineering decision and I would not fight you on it.
The second is that I have not stress-tested LangChain's mcp metadata namespace against a deliberately hostile server. Everything above about how the metadata is exposed comes from LangChain's changelog and the MCP specification, not from my own adversarial testing. The namespace is marked beta, importing from it raises a LangChainBetaWarning, and the migration off langchain-mcp-adapters removes MultiServerMCPClient along with other features, so the surface may move before it settles.
The third is that "put the guarantee in the network" is easy to write and expensive to do. Egress control for an agent that legitimately needs to reach a shifting set of external APIs is genuinely hard, and telling someone to sandbox their way out of the problem is not a plan. The honest version is that most teams will end up with a layered approach where annotations drive the prompt, an owned allowlist drives auto-approval, and network controls catch the class of failure the first two miss. That is more work than reading a boolean.
The fourth is that the defaults already do a lot of this work for you. A tool with no annotations is treated as destructive and open-world. If you never opt into trusting hints, the pessimistic default is your policy, and it is a decent one.
What to do with this
The question worth sitting with is not whether to use annotations. It is what your system does when one of them is wrong.
If a server flips destructive_hint to false on a tool that deletes things, walk through what happens in your stack. Does the approval prompt disappear? Does the tool land in an auto-run path? Does anything in your logs record that the hint changed? If the answer to the last one is no, that is the gap worth closing first, because the receipt is what turns a bad hint into an incident you can actually investigate.
The MCP maintainers gave the ecosystem a clear framework and a candid account of the limits. LangChain gave you the plumbing. What nobody can give you is the decision about which claims you are willing to act on, and that one is worth writing down before your agent makes it for you.
Sources: MCP Blog, "Tool Annotations as Risk Vocabulary" (16 March 2026) · LangChain Python changelog, langchain v1.4.0 (1 September 2026) · MCP specification, server tools · Simon Willison, "The lethal trifecta"
Medium metadata
Title: MCP's destructive_hint Is Not a Security Boundary, and LangChain v1.4.0 Just Made It Easier to Forget
Subtitle: Tool annotations are a claim the server makes about itself. Here is where the real gate goes.
Tags: MCP, AI Agents, LangChain, AI Security, Software Architecture
Recommended publication: Tkay Nation's Writes
Canonical: import from the fervorai.dev URL
Kicker for social: The default value of destructiveHint is true. That pessimism is the most honest thing in the annotation system, and it is the first thing people throw away.