MCP tool annotations (readOnlyHint, destructiveHint, openWorldHint) are the mechanism by which clients decide whether to auto-approve a tool call or prompt the user for confirmation. Without annotations, the MCP spec defaults destructiveHint to true and readOnlyHint to true — meaning a tool that permanently deletes files could be treated as read-only and auto-approved, bypassing any confirmation dialog. Clients that implement safe-by-default behavior rely entirely on these fields to gate destructive operations.
Low because missing annotations degrade client safety UX but do not themselves cause data loss — the risk depends on how aggressively the client auto-approves unannotated tools.
Set annotations on every tool that modifies state, deletes data, or calls external services. Read-only tools should explicitly mark readOnlyHint: true.
// src/tools/delete.ts
server.tool(
'delete_file',
{
description: 'Permanently delete a file from disk',
annotations: {
readOnlyHint: false,
destructiveHint: true,
openWorldHint: false,
},
},
{ path: z.string() },
async ({ path }) => { /* handler */ }
)
For pure read tools like search_files, set readOnlyHint: true, destructiveHint: false so clients can auto-approve them without a confirmation dialog.
ID: mcp-server.tool-definitions.tool-annotations
Severity: low
What to look for: Count all tools. Enumerate which have MCP annotations (readOnlyHint, destructiveHint, idempotentHint, openWorldHint) vs. which lack them. Check whether tools that perform side effects have appropriate annotations. The MCP spec defines readOnlyHint (defaults true — tool doesn't modify state), destructiveHint (defaults true — tool may perform destructive operations), and openWorldHint (defaults true — tool interacts with external systems). Check that write/delete tools have readOnlyHint: false and destructiveHint: true. Check that pure read tools have readOnlyHint: true. These annotations help clients display confirmation dialogs and make safer tool selection decisions.
Pass criteria: Tools that modify state set readOnlyHint: false. Destructive tools (delete, overwrite) set destructiveHint: true. Read-only tools explicitly set readOnlyHint: true. At least the most impactful tools have annotations. At least 80% of tools should have at least 1 behavior annotation.
Fail criteria: Tools that delete or overwrite data have no annotations, or annotations contradict the tool's actual behavior (e.g., readOnlyHint: true on a tool that writes files).
Skip (N/A) when: The server registers no tools, or all tools are read-only. All checks skip when no MCP server is detected.
Cross-reference: For capability declarations, see capability-declaration.
Detail on fail: "Tool 'delete_file' has no annotations — defaults to readOnlyHint: true which is incorrect for a destructive operation. Clients may auto-approve this tool when they shouldn't" or "Tool 'search_files' has destructiveHint: true but only reads files — annotation is misleading"
Remediation: Annotations help clients make safe decisions about auto-approving vs. confirming tool calls:
// src/tools/delete.ts — annotated tool
{ name: "delete_item", annotations: { destructiveHint: true, idempotentHint: false } }
server.tool(
'delete_file',
{
description: 'Permanently delete a file from disk',
annotations: {
readOnlyHint: false,
destructiveHint: true,
openWorldHint: false,
},
},
{ path: z.string() },
async ({ path }) => { /* handler */ }
)
server.tool(
'search_files',
{
description: 'Search for files matching a pattern',
annotations: {
readOnlyHint: true,
destructiveHint: false,
openWorldHint: false,
},
},
{ pattern: z.string() },
async ({ pattern }) => { /* handler */ }
)