Repository navigation
Publish MCP tool annotations derived from each tool's declared effect - #532
Merged
Merged
Conversation
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>
There was a problem hiding this comment.
🟢 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
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #518
Summary
tools/listnow gives every MCP tool a title andToolAnnotationsderived from its declaredeffect. 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, puretools/layer):Effectvalue to read-only, destructive, idempotent and open-world hints;validate_hintsruns over the whole registry at import, so a new effect, an untitled tool, or a new proposal tool without its own hints fails immediately.readOnlyHintdestructiveHintidempotentHintopenWorldHintcluster_readexternal_read(Prometheus, Loki)ui_onlycluster_write(never listed by MCP)propose_writeget_write_proposal,cancel_write_proposalKorvidMCPServer.list_toolsbuilds eachtypes.Toolwithtitleandannotations. The title is also set onToolAnnotations.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
--readonlystill removes writes.docs/mcp.mdsays 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: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.idempotentHintis left unset on read-only tools. The MCP specification reads it only for tools that are not read-only.tools/hints.py, not onToolDef.tools/registry.pyis at its source-size limit.Verification
Tests were written to fail first:
tests/tools/test_hints.py:Effectvalue has hints;cluster_writeorwrite_proposaltool is read-only, checked over the whole registry;tests/mcp/test_server.py::test_list_tools_publishes_each_tools_title_and_hintscovers every listed tool, with proposals and observability enabled.tests/mcp/test_stdio.py::test_stdio_forwards_tools_results_and_proposals_without_credentialsnow checks the relayed annotations end to end.uv run --frozen pytest -p no:tach tests/tools/test_hints.py tests/mcp: 266 passed, 1 skipped (pre-existing).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
make checkpasses locally.and a fail-closed audit entry, and sensitive reads are still masked.