MCPSafe.io
RegistryThreatsMethodologyDocsPricingScanSign in
MCPSafe.io

Security checks for MCP servers — public packages and private repos, fast or deep.

Legal

Privacy PolicyCookie PolicyTerms of ServiceSecurity disclosure

Resources

State of MCP SecuritySupportSystem statusMade in Germany 🇩🇪

© 2026 MCPSafe. All rights reserved.

GDPR — Privacy Policy
← Threat Catalog

Tool Definition & Lifecycle

Destructive Tool Annotation Mismatch

HIGHCWE: CWE-506Rule: MCP-200

MCP tools with destructive names or behaviors lack honest annotations, allowing LLM clients to invoke state-mutating operations silently without user confirmation.

What it is

The MCP specification defines tool annotations — specifically `destructiveHint` and `readOnlyHint` — as a contract between servers and clients that gates whether user confirmation is required before execution. Violations occur in two forms: a tool whose name implies destruction (e.g., `delete_*`, `wipe_*`, `wire_transfer_*`) is registered without `destructiveHint: true`, or a tool asserts `readOnlyHint: true` while its handler body contains destructive sinks such as `fs.unlink`, `DROP TABLE`, or `DELETE FROM`. Both patterns cause clients to incorrectly classify the tool's safety profile.

Why it matters for MCP

In traditional web APIs, a human explicitly chooses which endpoint to call and reviews its documentation before invoking it. In MCP, the LLM selects and invokes tools autonomously based on tool names, descriptions, and annotations — meaning a missing or false annotation directly determines whether the model executes a destructive action without any confirmation gate. Prompt injection attacks compound this risk: a malicious instruction in retrieved content can nudge the model to invoke a deceptively annotated `readOnly` tool that silently deletes records. The tool composition model also means a single dishonest annotation in a multi-step agentic pipeline can cascade into unrecoverable data loss.

Vulnerable example

example.js
1
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
3
const server = new McpServer({ name: "acme-users", version: "0.1.0" });
4
5
server.tool(
6
  "delete_user",
7
  "Permanently remove a user account.",
8
  async ({ userId }) => {
9
    await db.users.delete({ where: { id: userId } });
10
    return { ok: true };
11
  },
12
);

Secure example

example.js
1
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
3
const server = new McpServer({ name: "acme-users", version: "0.1.0" });
4
5
server.tool(
6
  "delete_user",
7
  "Permanently remove a user account.",
8
  async ({ userId }) => {
9
    await db.users.delete({ where: { id: userId } });
10
    return { ok: true };
11
  },
12
  { annotations: { destructiveHint: true } },
13
);

How MCPSafe detects this

MCPSafe matches `server.tool(...)` and `@mcp.tool(...)` registrations where the tool name matches a destructive-action pattern (`/^(delete|remove|wipe|drop|purge|erase|wire_transfer|truncate)_/i`) and the annotations object is absent or lacks `destructiveHint: true`; a second rule performs cross-statement taint analysis within the same file to flag any tool registration asserting `readOnlyHint: true` whose handler closure contains a destructive sink (`fs.unlink`, `fs.rm`, `os.remove`, `os.unlink`, SQL `DELETE`/`DROP`/`TRUNCATE` string literals, or HTTP calls with `method: DELETE`). Benign read-only tools with no destructive sinks and properly annotated destructive tools are explicitly excluded from both signals.

See the full threat catalog for every documented detection.

Further reading

  • MCP Specification: Tool Annotations
  • CWE-506: Embedded Malicious Code
  • MCP TypeScript SDK: server.tool() API
  • OWASP: Insufficient Authorization Controls

Scan an MCP server for this issue

MCPSafe runs this check — and every other rule in the catalog — on any MCP server you paste in.

Scan now