DEV Community

Ramdai Bista
Ramdai Bista

Posted on Originally published at agentkitworks.com

Three MCP Reference Servers Say 'Non-Destructive.' Two of Them Lie.

If you're wiring an agent up to MCP tools, you've probably seen destructiveHint: false on a tool definition and treated it as fact — maybe your client even auto-approves calls carrying it, because that's the whole point of the annotation. It's supposed to mean "this call only adds data, nothing you own can be lost."

Three open issues against the official modelcontextprotocol/servers repo show that annotation can be wrong, in three different servers, in three different ways. None of them are exotic edge cases. Here's what actually happens, and what to check before you trust any tool's own description of itself.

1. The memory server rewrites the whole file on every "additive" call

memory's create_entities, create_relations, and add_observations are all marked destructiveHint: false. In practice, each call reloads the entire knowledge graph, parses it against a fixed schema, and rewrites the whole JSONL file from scratch.

That's fine as long as every line in the file fits the schema. It isn't fine for anything the schema doesn't model: a createdAt field another client wrote, a meta record type, a malformed or truncated line. All of it gets silently dropped on the next "additive" write, and the response only reports what was added — there's no signal that anything disappeared.

The reproduction is simple: write a record with an extra field, then add an unrelated entity. The extra field is gone. Nothing errored. Nothing in the response mentioned it.

2. The git server can quietly reset a branch to a different commit

mcp-server-git's git_create_branch is also destructiveHint: false. The handler calls GitPython's create_head(branch_name, base) without first checking whether that branch already exists.

GitPython only refuses to overwrite a loose ref file. If the branch you're naming happens to be stored as a packed ref — which is routine after git gc, git pack-refs, or in a lot of fresh clones — there's no collision check, and the branch gets repointed to the new base. The tool still reports success: "Created branch." The previous tip becomes unreachable from every ref. There's no reflog entry recording the reset, because as far as git's reflog is concerned, nothing happened — the ref was repointed by Python, not by a normal git operation that logs itself.

The CLI equivalent, git branch <name>, just refuses outright in this situation. The MCP tool doesn't.

3. The memory server (again) can flip your file's permission bits — or overwrite a read-only file outright

A separate issue in the same family: saveGraph persists its state through a temp-file-then-rename, and the rename doesn't preserve the original file's permission bits. A file you'd explicitly set to 0600 came back as 0644 after a routine mutation — now readable by other users on the machine. Worse, a 0444 read-only file got overwritten instead of refused; the reporter confirmed the pre-regression code used to refuse that write, and a later commit silently dropped the check.

This one's already fixed upstream (merged into v2/main in early October), which is the good outcome. But it was in production long enough to matter, and the fix doesn't retroactively restore the permission bits on files it already touched.

Why "destructiveHint" can't just be trusted

The annotation exists so a client — or a human approving tool calls — can make a fast decision: auto-approve the safe ones, slow down for the dangerous ones. That only works if the annotation is actually derived from the tool's behavior. In all three cases here, it was derived from the intent of the tool ("this adds things") rather than its implementation (full rewrite from a lossy schema; no existence check before a ref move; a rename that doesn't preserve mode bits).

And because these are the official reference servers, the mislabeling doesn't stay contained — they're the thing everyone forks, copies, and ships as their own "memory server" or "git server," annotation included.

What to actually check

Don't take destructiveHint on faith for any server you didn't write, official or not:

  • Reproduce the "additive" claim. Write something the tool's schema doesn't model — an extra field, an unexpected shape — then make an unrelated additive call. If the first thing vanishes, the hint is wrong.
  • Check for existence before assuming "create" means "create." Any create_X tool that doesn't error on an existing X is a silent overwrite waiting for the one code path that actually has a collision (a packed ref, a cached lookup, whatever the implementation's blind spot is).
  • Read the persistence path, not the tool description. Temp-file-and-rename is common and usually fine — until you ask whether it preserves permission bits and read-only flags, which it often doesn't by default in Node or Python without you asking for it explicitly.
  • Assume the hint describes intent, not implementation, until you've read the handler yourself or seen someone else's reproduction.

A PreToolUse hook that inspects the shell commands your own agent is about to run — which is what most "agent safety" tooling, including ours, actually does — won't catch any of this. It has no visibility into what a remote MCP server does internally once your agent's call reaches it. That's a real, separate gap, not a reason to buy anything; it's just worth knowing which problem your current guardrails do and don't cover before you lean on an MCP tool's own metadata to decide what's safe.

Top comments (2)

Collapse
 
anciwasim profile image
Wasim Sheikh •

The annotation describing intent instead of implementation is the part that bites. One cheap check I'd want before trusting destructiveHint: false is a shadow run against a copied fixture that has the awkward cases in it: an extra field the schema doesn't model, a packed ref, a 0444 file. Run the additive call there, diff before and after, and only then let the hint drive auto-approve. Do you gate auto-approve on that kind of diff in CI, or does the annotation mostly get taken at face value?

Some comments may only be visible to logged-in visitors. Sign in to view all comments.