# Tool annotations (readOnlyHint, destructiveHint, openWorldHint) are set

- **Pattern:** `ab-001831` (`mcp-server.tool-definitions.tool-annotations`)
- **Severity:** low
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-001831
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-001831)

## Why it matters

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.

## Severity rationale

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.

## Remediation

Set annotations on every tool that modifies state, deletes data, or calls external services. Read-only tools should explicitly mark `readOnlyHint: true`.

```ts
// 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.

## Detection

- **ID:** `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:

  ```ts
  // src/tools/delete.ts — annotated tool
  { name: "delete_item", annotations: { destructiveHint: true, idempotentHint: false } }
  ```

  ```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 */ }
  )

  server.tool(
    'search_files',
    {
      description: 'Search for files matching a pattern',
      annotations: {
        readOnlyHint: true,
        destructiveHint: false,
        openWorldHint: false,
      },
    },
    { pattern: z.string() },
    async ({ pattern }) => { /* handler */ }
  )
  ```

## External references

- external mcp-spec-tool-annotations — https://modelcontextprotocol.io/docs/concepts/tools#tool-annotations

Taxons: inference-contract

HTML version: https://auditbuffet.com/patterns/ab-001831
