Skip to content

Commit 2b985dd

Browse files
committed
mcp(test[server]): integration test card hints against real registered tools
``test_card_omits_invisible_tools`` proves the visibility filter's *logic* using a synthetic 3-element ``visible_tool_names`` set. The production wiring uses ~20+ names returned by ``asyncio.run(mcp.list_tools())`` after ``mcp.enable(tags=..., only=True)`` runs in ``run_server``. The two paths share code but are not the same exercise. Adds an integration test that registers the real tool surface, applies the same ``mcp.enable(tags={TAG_READONLY}, only=True)`` call ``run_server`` makes, collects the visible names from ``mcp.list_tools()``, and asserts every hint phrase still appears in the card. All three current hint tools (``snapshot_pane``, ``wait_for_text``, ``search_panes``) carry ``TAG_READONLY``, so even the most restrictive tier keeps them visible — the test pins this invariant. A future change that moves a hint tool out of the readonly tier will fail this test and force a deliberate decision: retag the tool, or drop the hint from ``_HANDLE_HINTS``. The check is paired: each iteration asserts both that the hint tool is actually in the visible set (catches an unexpected tier change) and that the corresponding phrase is in the produced card (catches a wiring regression). Pure addition; no source changes.
1 parent 258ecfb commit 2b985dd

1 file changed

Lines changed: 45 additions & 0 deletions

File tree

tests/test_server.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,51 @@ def test_format_handles_section_empty_visible_set_emits_no_examples() -> None:
347347
assert "- Prompts — packaged workflows" in result
348348

349349

350+
def test_card_emits_hints_against_real_registered_tools() -> None:
351+
"""End-to-end: register_tools → mcp.enable → list_tools → _build_instructions.
352+
353+
``test_card_omits_invisible_tools`` proves the filter *logic* using a
354+
3-element synthetic ``visible_tool_names``. This test proves the
355+
production *wiring*: it registers the real tool surface, applies the
356+
same ``mcp.enable(tags=..., only=True)`` call ``run_server`` makes,
357+
collects the visible names from ``mcp.list_tools()``, and confirms
358+
every hint phrase still appears in the produced card.
359+
360+
All three current hint tools (``snapshot_pane``, ``wait_for_text``,
361+
``search_panes``) carry ``TAG_READONLY``, so even the most
362+
restrictive safety tier keeps them visible — the test pins this
363+
invariant. If a future change moves a hint tool out of the readonly
364+
tier, this test will fail and force a deliberate decision about
365+
whether to retain or drop the corresponding card hint.
366+
"""
367+
import asyncio
368+
369+
from fastmcp import FastMCP
370+
371+
from libtmux_mcp.tools import register_tools
372+
373+
mcp = FastMCP(name="card-wiring-integration")
374+
register_tools(mcp)
375+
# Mirror run_server's gating call exactly — readonly is the most
376+
# restrictive tier, so a hint phrase surviving here implies it
377+
# survives every higher tier too.
378+
mcp.enable(tags={TAG_READONLY}, only=True)
379+
380+
visible = {tool.name for tool in asyncio.run(mcp.list_tools())}
381+
card = _build_instructions(TAG_READONLY, visible_tool_names=visible)
382+
383+
for hint_tool, phrase in _VISIBILITY_FILTER_CASES:
384+
assert hint_tool in visible, (
385+
f"hint tool {hint_tool!r} unexpectedly hidden under readonly "
386+
f"tier; either retag the tool or drop the hint from "
387+
f"_HANDLE_HINTS"
388+
)
389+
assert phrase in card, (
390+
f"hint phrase {phrase!r} missing from card built against the "
391+
f"real registered tool set; production wiring is broken"
392+
)
393+
394+
350395
def test_registered_tools_accept_socket_name() -> None:
351396
"""All registered tools (except list_servers) accept ``socket_name``.
352397

0 commit comments

Comments
 (0)