Skip to content

feat(annotations): infer MCP tool annotations from naming convention#41

Merged
arapov merged 1 commit into
masterfrom
feat/tool-annotations
May 20, 2026
Merged

feat(annotations): infer MCP tool annotations from naming convention#41
arapov merged 1 commit into
masterfrom
feat/tool-annotations

Conversation

@arapov
Copy link
Copy Markdown
Collaborator

@arapov arapov commented May 20, 2026

Summary

Adds MCP `ToolAnnotations` (`readOnlyHint`, `destructiveHint`) to every registered tool by inferring from the strict naming convention the catalogue already follows. Clients like Claude Desktop and Claude Code use these hints for auto-approval — without them, users see a confirmation prompt for every tool call (including the safe read tools).

Inference rules

Pattern Count Annotation
Read-prefixed (`search_`, `filter_`, `get_`, `list_`, `show_`, `run_`) 49 `{ readOnlyHint: true }`
Destructive (5 whole-record deletes + `remove_track` + `remove_additional_party`) 7 `{ destructiveHint: true }`
Other writes (creates, updates, additive child writes, batches) 30 no annotation

49 + 7 + 30 = 86, asserted explicitly by a new aggregate-counts test in `tests/tool-annotations.test.ts`. A new tool added without matching the convention will drift the counts and trip CI, forcing an explicit annotation decision.

The destructive set matches the existing schema-level `confirm: true` gate — annotation is a separate, client-facing hint surfacing the same "needs confirmation" signal at the protocol layer.

What this changes for users

Client Before After
Claude Desktop / Code Prompts before every tool call Auto-approves the 49 read tools; prompts only for writes
Other MCP clients Same default as before Same default — annotations are hints, not commitments
Glama and other registries scraping `tools/list` Saw uncategorized tools See properly tagged read-only vs destructive — better quality scoring

Test plan

  • `npm run typecheck` clean
  • `npm run lint` clean
  • `npm run format:check` clean
  • `npm test` — 468 / 468 (was 463 + 5 new annotation tests)
  • `npm run build` — `dist/index.js 147.44 KB`, `dist/http.js 173.91 KB` (~1 KB growth for annotation literals)
  • Privacy sweep clean
  • Round-trip integration test verifies annotations actually appear in `tools/list` response (not just on the registration side)

🤖 Generated with Claude Code

Adds MCP `ToolAnnotations` (`readOnlyHint`, `destructiveHint`) to
every registered tool by inferring from the strict naming convention
the catalogue follows. Clients like Claude Desktop / Claude Code use
these hints for auto-approval — without them, users see a
confirmation prompt before every tool call.

Inference rules:

- Read prefixes (search_, filter_, get_, list_, show_, run_) →
  { readOnlyHint: true }. 49 tools.
- The 7 destructive tools (5 whole-record deletes + remove_track +
  remove_additional_party) → { destructiveHint: true }. Same set the
  existing schema-level `confirm: true` gate covers.
- All other writes (creates, updates, additive child writes, batches)
  → no annotation. Clients fall back to their default (prompt).
  30 tools.

49 + 7 + 30 = 86 — verified at the catalog level by a new
aggregate-counts assertion in tests/tool-annotations.test.ts. A new
tool added without matching the convention will drift the counts and
trip the test, forcing an explicit annotation decision.

Wired through both `registerTool` and `registerToolTask` so the
annotations carry through tools/list responses for the 5 batch_*
tasks-augmented writes too. `get_attachment` (raw `server.tool()`)
gets an explicit `{ readOnlyHint: true }` matching the auto-inferred
behaviour for every other `get_*` tool.

Tests: 463 → 468 (+5 — pure-function inference checks plus a
round-trip integration test asserting tools/list responses carry
the right hints).

Bundle delta: dist/index.js 146.52 → 147.44 KB, dist/http.js 173.00
→ 173.91 KB (annotation literals).
@arapov arapov merged commit c159896 into master May 20, 2026
1 check passed
@arapov arapov deleted the feat/tool-annotations branch May 20, 2026 09:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant