Every tool exposed by the Closient MCP server carries a structuredDocumentation Index
Fetch the complete documentation index at: https://docs.closient.com/llms.txt
Use this file to discover all available pages before exploring further.
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.| Field | Meaning |
|---|---|
readOnlyHint | true → the tool performs no state changes. Hosts may invoke without confirmation. |
destructiveHint | true → the tool may destructively modify user-visible state (delete, overwrite). |
idempotentHint | true → repeated calls with identical arguments yield identical effects. |
openWorldHint | false → the tool reads only from Closient-controlled data (catalog, resolver rules). |
title | Human-readable label for UIs that prefer a polished name over the snake-case tool name. |
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 ofdestructiveHint: 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
| Tool | readOnlyHint | destructiveHint | idempotentHint | Notes |
|---|---|---|---|---|
ping | true | — | true | Health probe; no auth, no I/O. |
validate_gtin | true | — | true | Pure validation — no DB access. |
resolve_gtin | true | — | true | DB read against the resolver-rule hierarchy. |
lookup_product | true | — | true | DB read against the product catalog. |
generate_qr_url | false | — | true | Builds a Digital Link URL and records the call via @user_action. State-mutating but not destructive. |
generate_qr_url is the lone non-read-only tool today. The decision to
omit destructiveHint is intentional and pinned by
test_no_tool_currently_marked_destructive in
backend/closient/tests/test_mcp_tool_annotations.py — the test fails
loudly if a future contributor flips the bit without updating policy.
Wire format
Annotations are serialized as a nested object on each entry of thetools/list response. Pydantic’s exclude_none=True mode drops fields
that weren’t set, so the wire payload is minimal:
curl:
Adding a new tool
When you register a new tool with@mcp.tool() in
backend/closient/mcp_tools.py:
- Pass
annotations=ToolAnnotations(...)with at least one ofreadOnlyHint/destructiveHintset. - Add a row to the per-tool table above and to the parametrized
test_tool_read_only_hint_matches_policytest. - If the tool is destructive, update
test_no_tool_currently_marked_destructiveso the assertion still reflects ground truth.
test_every_registered_tool_has_annotations is the safety
net — a missing annotation fails the suite before it can ship.