test(mcp): assert the agent-reachable tool surface is exactly the six memory_* tools

Closes out finding 5: rather than relying on inspection of
mcp_server.py, assert directly that the MCP tool list is exactly
{memory_search, memory_get, memory_write, memory_update,
memory_supersede, memory_flag} and that none of edit, merge_into,
set_state, approve, reject or hard_delete is reachable through it. No
production code change was needed — mcp_server.py already only
registers the six tools.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0139vHnNpS2sLNELjrAHX5Cu
This commit is contained in:
MarkusandClaude Opus 5 committed 2026-09-12 18:38:55 +00:00
1 parent 10bb14189d
commit 40710ad23a
1 file changed
+24
+24
View File
@@ -199,3 +199,27 @@ async def test_pending_vectors_are_retried_in_the_background(settings, monkeypat
assert service.health().vec_pending == 0
finally:
await service.stop()
async def test_mcp_surface_cannot_mutate_or_promote_an_approved_note(svc):
"""§2.4/§9: only the owner (via the web UI) may promote, edit in place, or
mutate lifecycle state; agents get exactly the six memory_* tools. This
closes out finding 5 by asserting the invariant in a test rather than
relying on inspection of mcp_server.py."""
from mcp.client import Client
from mdmem.mcp_server import build_mcp_server
async with Client(build_mcp_server(svc)) as client:
names = {t.name for t in (await client.list_tools()).tools}
assert names == {
"memory_search",
"memory_get",
"memory_write",
"memory_update",
"memory_supersede",
"memory_flag",
}
owner_only = {"edit", "merge_into", "set_state", "approve", "reject", "hard_delete"}
assert names.isdisjoint(owner_only)