Skip to content

Publish MCP tool annotations derived from each tool's declared effect - #532

Merged
hellices merged 3 commits into
mainfrom
feat/518-mcp-tool-annotations
Oct 31, 2026
Merged

hellices merged 3 commits into
mainfrom
feat/518-mcp-tool-annotations

Conversation

@hellices

Copy link
Copy Markdown
Owner

Closes #518

Summary

tools/list now gives every MCP tool a title and ToolAnnotations derived from its declared effect. A host can then auto-allow read-only tools and prompt before the rest, instead of treating every korvid tool the same.

  • src/korvid/tools/hints.py (new, pure tools/ layer):

    • maps every Effect value to read-only, destructive, idempotent and open-world hints;
    • refines the three proposal tools;
    • holds each tool's title.

    validate_hints runs over the whole registry at import, so a new effect, an untitled tool, or a new proposal tool without its own hints fails immediately.

    Tools readOnlyHint destructiveHint idempotentHint openWorldHint
    cluster_read true false unset false
    external_read (Prometheus, Loki) true false unset true
    ui_only true false unset false
    cluster_write (never listed by MCP) false true false false
    propose_write false true false false
    get_write_proposal, cancel_write_proposal false false true false
  • KorvidMCPServer.list_tools builds each types.Tool with title and annotations. The title is also set on ToolAnnotations.title, which older protocol revisions read. The stdio adapter relays both unchanged.

  • Approval does not depend on the hints. A proposal still waits for a keystroke in the TUI, and --readonly still removes writes. docs/mcp.md says so in a new "Tool hints" section; the release notes are updated.

Departures from the issue, recorded in docs/superpowers/plans/2026-10-31-mcp-tool-hints.md:

  • Destructive hint for proposals. The issue asks for it "per proposed operation", but one tool, propose_write, proposes every operation, deletes included, so it is marked destructive. Reading or cancelling a proposal is not destructive; per the issue's rule it is still not read-only.
  • idempotentHint is left unset on read-only tools. The MCP specification reads it only for tools that are not read-only.
  • Titles live in tools/hints.py, not on ToolDef. tools/registry.py is at its source-size limit.

Verification

Tests were written to fail first:

  • tests/tools/test_hints.py:
    • every tool has a unique title;
    • every Effect value has hints;
    • no cluster_write or write_proposal tool is read-only, checked over the whole registry;
    • each effect's row of the table;
    • the three import-time rejections.
  • tests/mcp/test_server.py::test_list_tools_publishes_each_tools_title_and_hints covers every listed tool, with proposals and observability enabled.
  • tests/mcp/test_stdio.py::test_stdio_forwards_tools_results_and_proposals_without_credentials now checks the relayed annotations end to end.
  • Mutation checks on every mapping row, refinement, validation branch and published field: all caught.
  • uv run --frozen pytest -p no:tach tests/tools/test_hints.py tests/mcp: 266 passed, 1 skipped (pre-existing).
  • Full gate run locally (check_source_size, ruff check, ruff format --check, mypy, tach check, pytest -n 8): 15501 passed. The one failure, tests/providers/test_device_login_prefixes.py::test_the_measured_release_is_the_installed_one, happens only locally, because the installed litellm differs from the measured release.

Checklist

  • This closes an accepted issue in this repository that is assigned to me.
  • Tests cover the change, written to fail first where behavior changed.
  • make check passes locally.
  • User-facing docs and the README match the change, or nothing user-facing changed.
  • No security invariant is weakened: writes still need a keystroke approval
    and a fail-closed audit entry, and sensitive reads are still masked.

hellices and others added 2 commits October 31, 2026 18:02
tools/hints.py derives readOnlyHint, destructiveHint, idempotentHint and
openWorldHint from each tool's registered effect, refines the three
proposal tools (propose_write is destructive; reading or cancelling a
proposal is not, and is idempotent), and gives every tool a title. A new
effect, an untitled tool or an unrefined proposal tool fails at import.

tools/list publishes the title and annotations for every tool, and the
stdio adapter relays them. The hints are for the host's prompting only:
approval never relies on them.

Part of #518

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
docs/mcp.md gains a Tool hints section: what each family is annotated
with, and that korvid's approval never relies on the hints. Release notes
and the plan record the slice.

Part of #518

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings October 31, 2026 09:03
@hellices hellices added this to the v0.7.0 milestone Oct 31, 2026
@hellices hellices added enhancement New feature or request area: mcp Model Context Protocol server and external host integration labels Oct 31, 2026
@hellices hellices self-assigned this Oct 31, 2026
Copilot AI previously approved these changes Oct 31, 2026

Copilot AI 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.

🟢 Approved

No active review issues block approval of the MCP tool-title and annotation changes.

No open issues 🎉

What changed in this PR

Publishes MCP tool titles and effect-derived annotations so hosts can distinguish read-only tools from write proposals, addressing #518 without changing korvid’s approval controls.

Changes:

  • Adds centralized titles, effect-to-hint mappings, and validation for registered tools.
  • Publishes annotations through tools/list, with mapping, server, and stdio regression coverage.
  • Documents hint semantics, proposal-specific behavior, and unchanged TUI approval requirements.
File Description
tests/​tools/​test_hints.py Tests titles, effect mappings, write safety, and validation failures.
tests/​mcp/​test_stdio.py Checks annotations survive stdio forwarding.
tests/​mcp/​test_server.py Verifies published tool titles and annotations.
src/​korvid/​tools/​hints.py Defines and validates titles and effect-derived hints.
src/​korvid/​mcp/​server.py Adds titles and annotations to listed tools.
docs/​superpowers/​plans/​2026-10-31-mcp-tool-hints.md Records implementation steps and departures from the issue.
docs/​release-notes/​unreleased.md Announces MCP tool hints.
docs/​mcp.md Explains annotations and their separation from approval controls.

Review effort: Balanced

@my-reviewer-agent my-reviewer-agent 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.

이슈 1건을 인라인 코멘트로 남겼습니다.

check_effects runs at import over every Effect value, so a new effect
that no tool uses yet still fails immediately instead of only in the
tests.

Part of #518

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI 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.

🟢 Approved

No active review issues are recorded for the MCP tool titles and annotations change.

No open issues 🎉

Review effort: Balanced

@hellices
hellices merged commit 0bce327 into main Oct 31, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: mcp Model Context Protocol server and external host integration enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Publish MCP tool annotations derived from each tool's declared effect

2 participants