import sys from pathlib import Path import pytest from fastmcp import Client from fastmcp.client.transports.stdio import PythonStdioTransport ROOT = Path(__file__).resolve().parent.parent def _stdio_transport(env: dict[str, str] | None = None) -> PythonStdioTransport: return PythonStdioTransport( "server.py", cwd=str(ROOT), python_cmd=sys.executable, env=env, ) class TestMcpStdioIntegration: @pytest.mark.asyncio async def test_real_server_lists_registered_tools(self): async with Client(_stdio_transport()) as client: tools = await client.list_tools() tool_names = sorted(tool.name for tool in tools) assert tool_names == [ "get_loki_label_values", "get_o11y_delta_path", "list_loki_labels", "list_loki_series", "list_metric_label_values", "list_metric_labels", "list_metrics", "query_instant", "query_loki_instant", "query_loki_range", "query_range", "query_topsql", "read_delta_tool", ] @pytest.mark.asyncio async def test_query_range_returns_auth_error_over_real_mcp_transport(self): async with Client(_stdio_transport(env={})) as client: result = await client.call_tool( "query_range", { "query": "up", "start": "2024-01-01T00:00:00Z", "end": "2024-01-01T00:10:00Z", "step": "1m", "cluster_id": "c1", "env": "dev", "deploy_type": "starter", }, ) assert result.structured_content is not None payload = result.structured_content["result"] assert payload[0]["type"] == "text" assert "GITHUB_TOKEN" in payload[0]["text"] @pytest.mark.asyncio async def test_client_accepts_mcpservers_dict_config(self): config = { "mcpServers": { "tidb-observability": { "command": sys.executable, "args": ["server.py"], "cwd": str(ROOT), "env": {}, } } } async with Client(config) as client: tools = await client.list_tools() assert {tool.name for tool in tools} == { "get_loki_label_values", "get_o11y_delta_path", "list_metric_label_values", "list_metric_labels", "list_loki_labels", "list_loki_series", "list_metrics", "query_instant", "query_loki_instant", "query_loki_range", "query_range", "query_topsql", "read_delta_tool", }