Skip to content

mcp: advertise readOnlyHint so plan-mode clients stop prompting on every call - #699

Draft
poelzi wants to merge 1 commit into
justrach:release/0.2.5842from
poelzi:fix/mcp-readonly-annotations
Draft

mcp: advertise readOnlyHint so plan-mode clients stop prompting on every call#699
poelzi wants to merge 1 commit into
justrach:release/0.2.5842from
poelzi:fix/mcp-readonly-annotations

Conversation

@poelzi

@poelzi poelzi commented Aug 20, 2026

Copy link
Copy Markdown

Problem

tools/list ships no annotations at all, so every client that gates on annotations.readOnlyHint has to assume codedb's navigation tools might have side effects.

In Claude Code that is not just an extra prompt. In plan mode the permission evaluator applies an MCP-specific gate:

if (e.mcpInfo && !e.isReadOnly(g) && l.behavior === "passthrough" && mode === "plan" && ...)
    l = { behavior: "ask", decisionReason: { type: "mode", mode: "plan" } };

and then returns that ask before the allow-rule lookup runs. Deny and ask rules are consulted earlier, allow rules later — so in plan mode a configured mcp__codedb__* allow rule is never reached. The gate's only escape is isReadOnly(), which is defined as annotations?.readOnlyHint ?? false.

"Don't ask again" does not stick either: the plan-mode branch replaces the decision object wholesale, dropping the addRules suggestions that MCP's own checkPermissions normally attaches, so there is no persistence path.

Probing the server directly confirms the input side:

$ codedb mcp   # tools/list
codedb_outline  annotations = null
codedb_symbol   annotations = null
codedb_search   annotations = null
...

Net effect today: every codedb_search / codedb_symbol / codedb_read call prompts while planning, and no configuration can silence it.

Fix

Add "annotations":{"readOnlyHint":true} to the 19 tools that only read the index.

codedb_index rebuilds the on-disk index, and codedb_bundle can dispatch it, so neither is annotated — they should keep prompting.

The core / slim / mini profile builders and buildAugmentedToolsList all derive from this same static JSON, so the hint rides along on every profile with no extra code.

Verification

  • New test mcp: read-only tools advertise readOnlyHint on every profile asserts the hint across full, core, slim and mini, and asserts that codedb_index / codedb_bundle never claim to be read-only.
  • Confirmed over a live stdio handshake on the default mini profile (the six tools an agent client actually sees):
tool count: 6
  codedb_outline     annotations={'readOnlyHint': True}
  codedb_symbol      annotations={'readOnlyHint': True}
  codedb_search      annotations={'readOnlyHint': True}
  codedb_callers     annotations={'readOnlyHint': True}
  codedb_context     annotations={'readOnlyHint': True}
  codedb_deps        annotations={'readOnlyHint': True}
  • zig build test passes (the one unrelated test_index issue-690 inotify crash on this base is addressed in a separate PR).

Every MCP client with a plan or read-only mode benefits, not just Claude Code.

tools/list shipped no `annotations` at all, so every client that gates on
readOnlyHint treats codedb's navigation tools as potentially side-effecting.

In Claude Code's plan mode this is not just a prompt: the permission
evaluator's MCP gate rewrites the decision to "ask" before the allow-rule
lookup runs, so a configured `mcp__codedb__*` allow rule is never consulted
and "don't ask again" has no persistence path. The gate's only escape is
`annotations.readOnlyHint`. Net effect today: every codedb_search /
codedb_symbol / codedb_read call prompts while planning.

Annotate the 19 tools that only read the index. codedb_index rebuilds the
on-disk index and codedb_bundle can dispatch it, so both stay unannotated.
The profile builders (core/slim/mini) and the augmented bundle list all
derive from this same static JSON, so the hint rides along on each —
verified over a live stdio handshake on the default mini profile.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TqTyTJVJ4cd1MzFF54Hrbh
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