Files
pad/internal
xarmian 727cd80927 fix(mcp): explicit tool annotations from catalog write-shape knowledge (BUG-2302) (#1121)
mcp-go's NewTool injects default annotations on every tool —
ReadOnlyHint:false, DestructiveHint:true, OpenWorldHint:true — and
buildToolFromDef never overrode them, so every Pad tool advertised
itself as destructive, including pure reads like pad_search and
pad_project. Hosts use destructiveHint to decide whether to prompt;
mislabeling reads trains users to click through prompts.

Derive the block in buildToolFromDef from the catalog's own knowledge
(readOnlyActions — the same single source the tool-surface serializer
uses — plus a new sibling additiveWriteActions allowlist):

- every action read-only → ReadOnlyHint:true, DestructiveHint:false,
  IdempotentHint:true (pad_search, pad_project, pad_attachment,
  pad_meta, pad_playbook);
- writes all purely ADDITIVE → ReadOnlyHint:false,
  DestructiveHint:false (pad_workspace: invite/create/claim/restore;
  pad_library: activate — codex round 1: marking additive writes
  destructive reintroduces the prompt-training harm at tool level);
- any overwrite/delete-capable action → the conservative
  ReadOnlyHint:false, DestructiveHint:true (pad_item, pad_collection,
  pad_role) — unchanged on the wire from the old defaults;
- OpenWorldHint:false everywhere (pad tools are closed-world).

pad_set_workspace gets a hand-written block (write, non-destructive,
idempotent, closed-world).

Also adds the missing pad_item.history entry to readOnlyActions —
documented read-only since v0.14 but reported read_only:false on the
tool-surface descriptor.

ToolSurfaceVersion 0.19 → 0.20 (behavior bump, v0.9/v0.16 precedent):
no tool names, action enums, or param shapes changed. instructions.md
and README headings retitled per the drift tests. The changelog entry
describes only this change; BUG-2305 appends to it if it ships in the
same window (one bump total).

Tests: TestCatalogTools_AnnotationsExplicit pins a literal per-tool
read/additive/destructive table (deliberate second enumeration — a new
tool, or a write action added to an all-read or all-additive tool,
fails loudly until someone decides its class);
TestAdditiveWriteActions_NoStaleEntries guards the new allowlist
(real catalog pairs only, never overlapping readOnlyActions);
TestSetWorkspaceTool_AnnotationsExplicit covers both deployment
variants; pad_item.history joins the read spot-checks.
Mutation-verified both directions: destructive-polarity flip fails 10
tools; always-destructive fails the two additive rows.

Claude-Session: https://claude.ai/code/session_018qREYgDd6Ag1X1SDmqhyFM
2026-08-16 13:49:00 -04:00
..
2026-03-26 01:52:36 +00:00