Skip to content

thin mcp: declare ToolAnnotations so approval-gating hosts can pass (#86) - #103

Merged
tzhouam merged 2 commits into
mainfrom
fix/thin-mcp-tool-annotations
Aug 25, 2026
Merged

thin mcp: declare ToolAnnotations so approval-gating hosts can pass (#86)#103
tzhouam merged 2 commits into
mainfrom
fix/thin-mcp-tool-annotations

Conversation

@tzhouam

@tzhouam tzhouam commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

What

Declares MCP ToolAnnotations on all seven thin-server tools, so hosts that gate
un-annotated tools behind per-call approval can auto-approve the read-only surface:

tool readOnlyHint openWorldHint
review false (reserves a Strict run; Strict child reaches the network) true
validate_direct_review, get_review_status, get_review_result, update_knowledge, doc_search, doc_read true false

review also carries destructiveHint=false. The [mcp] extra floor moves 1.2 → 1.8
the first version verified (installed and checked) to ship ToolAnnotations and the
decorator kwarg; tested against 1.29.0.

Why (from #86's triage)

The reported "Strict mode crashes codex" reproduces on our side as a codex-side approval
flow
, not a server fault:

  • Driven directly over MCP, review(mode=strict) returns its run_id in 161 ms; the
    server is healthy (and its durable-run reconciliation behaved correctly when the owner
    died mid-test).
  • Under codex exec (0.145.0), the same call fails instantly with
    user cancelled MCP tool call; RUST_LOG debug shows codex raising
    mcp_tool_call_approval_exec-… for the un-annotated tool and auto-resolving it as
    Cancel under headless approval: never. Interactively, that same flow is an approval
    dialog — where the reported crash lives, before the server does any work.

Truthful annotations let annotation-aware codex versions auto-approve the six read-only
tools; review stays honestly non-read-only.

Also

  • doc/guide/hosts/codex.md: new "已知行为:MCP 工具审批" section documenting the
    interactive approval dialog and the headless auto-cancel (plus the existing
    startup_timeout_sec=120 installer setting for slow first uvx startup).
  • doc/architecture/SPEC/thin_mcp_server.md: re-verified (2026-08-25) with the new
    invariant — hints must stay truthful; marking a writing tool read-only is worse than not
    marking it.
  • Tests: the fake FastMCP now records tool kwargs and fakes mcp.types; a new guardrail
    test pins the annotation surface. Verified against real FastMCP too (list_tools()
    round-trip).

Verification

  • pytest test/test_thin_mcp_server.py test/test_thin_mcp.py and the full offline suite — green
  • tools/check_doc_links.py, tools/check_doc_citations.py — OK
  • tools/check_spec_freshness.py --strict — this branch adds no stale page
    (thin_mcp_server re-verified; the 11 stale pages it reports are pre-existing on main,
    identical with this change stashed)

Refs #86 (needs-info from the reporter still pending for their exact codex version/crash text).

🤖 Generated with Claude Code

https://claude.ai/code/session_0165QsXAeLVPrKfj8Yk5zns3

Root-caused from #86's triage: codex raises a per-call approval
elicitation for MCP tools without annotations (mcp_tool_call_approval_exec),
cancels them outright in headless runs ("user cancelled MCP tool call"),
and its interactive approval dialog is where the reported crash lives —
the server itself returns review(mode=strict)'s run_id in 161 ms.

Every tool now declares truthful hints: `review` is the only tool that
mutates state (reserves a Strict run) or reaches the network
(readOnlyHint=false, destructiveHint=false, openWorldHint=true); the other
six are readOnlyHint=true. The mcp extra floor moves 1.2 -> 1.8, the first
version verified to carry ToolAnnotations and the decorator kwarg.

The fake FastMCP in the tests records tool kwargs and fakes mcp.types; a
new guardrail test pins the read-only surface. codex host doc gains a
"known behavior" section for the approval dialog and the headless
auto-cancel; SPEC page re-verified with the new invariant.

Refs #86.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0165QsXAeLVPrKfj8Yk5zns3

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ad8c3cda58

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +956 to +957
read_only = ToolAnnotations(
readOnlyHint=True, idempotentHint=True, openWorldHint=False)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Do not mark reconciling poll tools read-only

When an unfinished run's owner and child have died, both get_review_result and get_review_status call CopilotMCP.get_result/get_status, which invoke reconcile_if_dead and rewrite run_status.json to interrupted (and even ordinary polls create/truncate the status lock file). Reusing this readOnlyHint=True annotation therefore lets approval-gating hosts auto-approve tools that can mutate durable run state, contradicting the annotation's security contract; either make polling observational or give these two tools non-read-only annotations.

Useful? React with 👍 / 👎.

@tzhouam

tzhouam commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator Author

Behavioral e2e on codex-cli 0.145.0 — the annotations demonstrably flip the outcome.

Same isolated codex exec invocation (--ignore-user-config, sandboxed, only this MCP server registered), same read-only call, two servers:

server result
this branch (annotated) imcopilot/doc_search (completed) — auto-approved, executed, real knowledge results returned
origin/main (no annotations) user cancelled MCP tool call — the approval elicitation auto-cancelled

So this is not only for future annotation-aware versions: 0.145.0 already auto-approves readOnlyHint=true tools and gates un-annotated ones. Also verified over a raw stdio MCP session that all seven tools serve their annotations on the wire, and a full live Strict review ran end-to-end through this server (review → poll → done in 9.4 min, $0.11, report paged back).

@tzhouam
tzhouam merged commit d4e87ed into main Aug 25, 2026
2 checks passed
@tzhouam
tzhouam deleted the fix/thin-mcp-tool-annotations branch August 25, 2026 07:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant