✨ WP14: console MCP tool metadata (annotations + parameter descriptions) - #19
Merged
Conversation
The console half of core's WP13 contract (tests/test_mcp_tool_metadata.py), written first and RED: 41 of 45 fail against main. Enumerates the console wrapper tools dynamically from BOTH surfaces — the stdio server the plugin runs (observe_mcp.build_mcp) and the Docker HTTP mount (routes.mcp._build_http_mcp) — so a future tool cannot ship without metadata, and asserts per surface: * ToolAnnotations declared, expected readOnly/destructive/idempotent hints, openWorldHint False everywhere (local SQLite is a closed world). Read via model_dump(by_alias=True) so the camelCase wire names hold on BOTH mcp majors (1.x fields are camelCase; 2.0 renamed them snake_case with camelCase aliases). * 100% inputSchema parameter-description coverage. * A "Use ..." when-to-use sentence in every description, namespace defined as an isolation key wherever it appears, siblings cross-referenced, and the non-obvious side effects disclosed (extraction, supersession, the console event log, lazy proposal expiry). * Surface parity extended from names/signatures (§6.3) to descriptions, inputSchema and annotations — the two surfaces must send identical metadata. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B1GNoTx3qTPrb8GQcxooWj
Gives the console's six wrapper tools the treatment core got in WP13
(v0.2.3): honest ToolAnnotations, a described parameter on every schema
property (34/34 across both surfaces), and a when-to-use sentence plus
sibling cross-references in every description. This is all a directory
(Glama) or an agent ever sees of a tool.
memory_add + memory_search move into mcp_tools.register_memory_tools, the
module that already owns the four maintenance tools, and both surfaces
register from there. Previously each surface defined them separately with
diverging one-line docstrings ("observing wrapper" / "HTTP wrapper"); shared
registration makes §6.3 parity structural instead of aspirational — metadata
cannot drift because there is one definition.
Annotations chosen per tool (openWorldHint=False on all six — every call
touches only local SQLite under the console data root):
* memory_add — readOnly False, destructive False (ADD-only spine: writes
supersede, never erase), idempotent False (each call writes new facts).
* memory_search — readOnly True: it never adds, supersedes or deletes a fact.
It does append one observability row to the console event log, which the
description discloses.
* memory_maintenance_run — readOnly False (apply=True claims the lease and
writes), destructive False, idempotent False.
* memory_maintenance_status — readOnly True: a pure model-free ledger read.
* memory_review_queue — readOnly False: listing lazily EXPIRES overdue
proposals, which is a write. destructive False.
* memory_review_decide — readOnly False, destructive False (approvals
supersede rather than erase), idempotent False.
Metadata only: tool names, parameters, handler bodies and return payloads are
unchanged, and the generated inputSchema is structurally identical to main
(diffed with descriptions stripped) — no k/limit minimums were added, unlike
WP13.
Console 198 passed (153 baseline + 45 new); core 319 unchanged.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B1GNoTx3qTPrb8GQcxooWj
… tools (WP14 review)
Review findings on the WP14 metadata work.
CRITICAL — `memory_search` claimed `readOnlyHint=True` while the console
wrapper writes. MCP's `readOnlyHint` means "does not modify its ENVIRONMENT",
and `gateway.search()` unconditionally appends a 'search' row to `_events.db`
(pruning past CAP=10 000) and creates the namespace store file on first touch.
Clients use the hint to auto-approve calls without confirmation, so a false
True is the worst-case direction — and the branch already applied the correct
standard to `memory_review_queue` ("listing lazily EXPIRES overdue proposals").
Now `readOnlyHint=False` / `destructiveHint=False` / `idempotentHint=False`,
with the docstring leading with the write instead of "read-only with respect to
memory". One standard, no telemetry carve-out; core's `memory_search` stays
`True` because core has no event log.
IMPORTANT — restored the WP13 first-call model-download disclosure, dropped in
the console port. `ConsoleConfig.models` defaults to "auto" and `_build_memory`
upgrades to SentenceTransformerEmbedder/CrossEncoderReranker whenever
`sentence_transformers` imports, and that embedder is lazy — so on a
`lean-memory[models]` install the first `memory_add`/`memory_search` blocks for
minutes on a ~1.2 GB download with nothing in the tool metadata saying so.
Minors: scoped the module docstring's metadata-parity claim to the console's two
surfaces (descriptions deliberately diverge from core's); `idempotentHint=True`
on `memory_review_queue` (expiry is idempotent — a re-poll is safe); moved the
orphaned annotation-baseline comment above the first `@mcp.tool`; de-duplicated
the superset paragraph, leaving observe_mcp.py as its one home.
Contract test pins all of it: EXPECTED_HINTS updated, the "read-only" assert
replaced with "never adds, supersedes or deletes" + "not read-only", and the
core `download` assert ported. Both new asserts verified to fail when the fix is
reverted.
Recorded in the WP14 packet row: the two deliberate core divergences, the
skipped `ge=1` on k/limit (metadata-only — a minimum would reject `k=0`), and
the dual-major evidence.
Console suite: 198 passed on mcp 1.28.1, 198 passed on a scratch venv pinned to
mcp==2.0.0.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B1GNoTx3qTPrb8GQcxooWj
Wuesteon
added a commit
that referenced
this pull request
Aug 6, 2026
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B1GNoTx3qTPrb8GQcxooWj
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 #13.
Gives the console's MCP wrapper tools the WP13 treatment: honest
ToolAnnotations(readOnlyHint/destructiveHint/idempotentHint,openWorldHint=Falseon all six tools), 100% inputSchema parameter-description coverage (34/34 properties), and when-to-use guidance + sibling cross-references in every description. Contract pinned inconsole/tests/test_mcp_tool_metadata.py(45 tests, wire-name asserts viamodel_dump(by_alias=True), holds on both mcp majors).Structural (behavior-preserving):
memory_add/memory_searchwere defined twice (stdio + HTTP mount) with drift-prone duplicated docstrings; they now register from a sharedmcp_tools.register_memory_tools, so surface parity is structural. Handler bodies verbatim; schema-minus-metadata proven identical to main by dump-diff.Deliberate divergences from core (recorded in the WP14 packet section):
memory_searchisreadOnlyHint=Falsehere — the console wrapper appends asearchevent row (+ prunes past cap) and creates the namespace store file on first touch; core's search has no event log so itsTruestays truthful.memory_review_queuecarriesidempotentHint=True(lazy expiry is idempotent); aligning core is a recorded follow-up.ge=1minimums copied from core — that would be a behavior change, out of scope for a metadata packet.Verification (all run, not assumed):
console/.venv): 198 passed (153 baseline + 45 new), 0 failuresmcp.client.stdiosession,list_toolsverified annotations + full description coveragegit diff main --stat -- src/ tests/ bench/empty; core suite 319 passedlean-memory-consoleis NOT independently listed (Glama keys on the GitHub repo, both packages ship from one repo) — packet done anyway as hygiene/parity; recorded🤖 Generated with Claude Code
https://claude.ai/code/session_01B1GNoTx3qTPrb8GQcxooWj