1+ from __future__ import annotations
2+
3+
14from unittest .mock import MagicMock , patch
25
36import pytest
47
8+ from fastmcp .client import Client
9+ from fastapi .routing import APIRoute
10+
511from src .api import app
612from src .mcp .server import build_mcp
713
@@ -27,3 +33,34 @@ def test_build_mcp_uses_fastapi_adapter():
2733 again = build_mcp (app )
2834 assert again is mock_mcp
2935 mock_factory .assert_called_once ()
36+
37+
38+ @pytest .mark .asyncio
39+ async def test_mcp_tools_cover_registered_routes ():
40+ mcp_server = build_mcp (app )
41+
42+ async with Client (mcp_server ) as client :
43+ tools = await client .list_tools ()
44+
45+ tool_by_name = {tool .name : tool for tool in tools }
46+
47+ expected = {}
48+ for route in app .routes :
49+ if not isinstance (route , APIRoute ) or not route .include_in_schema :
50+ continue
51+ tag = route .tags [0 ].lower ()
52+ tool_name = f"{ route .name } _{ tag } s"
53+ expected [tool_name ] = route
54+
55+ assert set (tool_by_name ) == set (
56+ expected
57+ ), "Every FastAPI route should be exported as an MCP tool"
58+
59+ for tool_name , route in expected .items ():
60+ schema = tool_by_name [tool_name ].inputSchema or {}
61+ required = set (schema .get ('required' , []))
62+ path_params = {param .name for param in route .dependant .path_params }
63+ # Path parameters must be represented as required MCP tool arguments
64+ assert path_params .issubset (
65+ required
66+ ), f"{ tool_name } missing path params { path_params - required } "
0 commit comments