From 92ef594b6a196a8084187d163058b82250695e76 Mon Sep 17 00:00:00 2001 From: haosenwang1018 Date: Mon, 13 Apr 2026 01:07:08 +0000 Subject: [PATCH] fix(mcp): hint on stdio protocol pollution --- nanobot/agent/tools/mcp.py | 18 +++++++++++++++++- tests/tools/test_mcp_tool.py | 27 +++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/nanobot/agent/tools/mcp.py b/nanobot/agent/tools/mcp.py index 1b5a71322..2aea19279 100644 --- a/nanobot/agent/tools/mcp.py +++ b/nanobot/agent/tools/mcp.py @@ -454,7 +454,23 @@ async def connect_mcp_servers( return name, server_stack except Exception as e: - logger.error("MCP server '{}': failed to connect: {}", name, e) + hint = "" + text = str(e).lower() + if any( + marker in text + for marker in ( + "parse error", + "invalid json", + "unexpected token", + "jsonrpc", + "content-length", + ) + ): + hint = ( + " Hint: this looks like stdio protocol pollution. Make sure the MCP server writes " + "only JSON-RPC to stdout and sends logs/debug output to stderr instead." + ) + logger.error("MCP server '{}': failed to connect: {}{}", name, e, hint) try: await server_stack.aclose() except Exception: diff --git a/tests/tools/test_mcp_tool.py b/tests/tools/test_mcp_tool.py index da90c4d0d..a133f53db 100644 --- a/tests/tools/test_mcp_tool.py +++ b/tests/tools/test_mcp_tool.py @@ -356,6 +356,33 @@ async def test_connect_mcp_servers_enabled_tools_warns_on_unknown_entries( assert "Available wrapped names: mcp_test_demo" in warnings[-1] +@pytest.mark.asyncio +async def test_connect_mcp_servers_logs_stdio_pollution_hint( + monkeypatch: pytest.MonkeyPatch, +) -> None: + messages: list[str] = [] + + def _error(message: str, *args: object) -> None: + messages.append(message.format(*args)) + + @asynccontextmanager + async def _broken_stdio_client(_params: object): + raise RuntimeError("Parse error: Unexpected token 'INFO' before JSON-RPC headers") + yield # pragma: no cover + + monkeypatch.setattr(sys.modules["mcp.client.stdio"], "stdio_client", _broken_stdio_client) + monkeypatch.setattr("nanobot.agent.tools.mcp.logger.error", _error) + + registry = ToolRegistry() + stacks = await connect_mcp_servers({"gh": MCPServerConfig(command="github-mcp")}, registry) + + assert stacks == {} + assert messages + assert "stdio protocol pollution" in messages[-1] + assert "stdout" in messages[-1] + assert "stderr" in messages[-1] + + @pytest.mark.asyncio async def test_connect_mcp_servers_one_failure_does_not_block_others( monkeypatch: pytest.MonkeyPatch,