Skip to main content
Every tool exposed by the Closient MCP server carries a structured annotations object that tells the host (Claude, Claude Desktop, the Connectors Directory validator, third-party MCP clients) whether the tool mutates state and whether repeated calls are safe to retry. This is required by the MCP specification and by the Anthropic Connectors Directory submission process — servers that omit the annotations are rejected at submission time.

Annotation fields

Each tool sets a subset of the following hints. They are advisory — agents and hosts use them to decide whether to gate a call behind a confirmation prompt, or whether retrying after a transport error is safe. readOnlyHint=true and destructiveHint=true are mutually exclusive. A CI test (test_destructive_hint_implies_not_read_only) enforces this.

Closient’s policy

We use a narrow reading of destructiveHint: it means destructive changes — deletes, overwrites, revocations, cancellations — not “any mutation.” A tool that adds a record without erasing prior state is flagged readOnlyHint=false without destructiveHint. The trade-off: destructiveHint=true causes most hosts to render an extra “are you sure?” prompt before the call. That’s the right UX before deleting a brand claim or canceling a subscription, and the wrong UX before generating a QR code or recording an audit event. We reserve the hint for the former.

Per-tool annotations

generate_qr_url was the lone non-read-only tool through C-344; C-4038 added the brand-owner product write tools above, five of which (delete_product_image, delete_product_document, delete_product_video, set_product_nutrition, set_product_ingredients) are the first tools to carry destructiveHint=true — they either delete a record outright or wholesale-replace a panel/list, dropping anything the caller didn’t re-list. The current destructive set is pinned by test_destructive_tools_match_pinned_set in backend/closient/tests/test_mcp_tool_annotations.py — the test fails loudly if a future contributor flips a bit without updating policy.

Wire format

Annotations are serialized as a nested object on each entry of the tools/list response. Pydantic’s exclude_none=True mode drops fields that weren’t set, so the wire payload is minimal:
You can verify the shape against a running server with the MCP Inspector or a one-off curl:

Adding a new tool

When you register a new tool with @mcp.tool() in backend/closient/mcp_tools.py:
  1. Pass annotations=ToolAnnotations(...) with at least one of readOnlyHint / destructiveHint set.
  2. Add a row to the per-tool table above and to the parametrized test_tool_read_only_hint_matches_policy test.
  3. If the tool is destructive, update test_destructive_tools_match_pinned_set so the assertion still reflects ground truth.
The CI test test_every_registered_tool_has_annotations is the safety net — a missing annotation fails the suite before it can ship.