diff --git a/scripts/test-windows.ps1 b/scripts/test-windows.ps1 index 8c1a8358a..0022560f3 100644 --- a/scripts/test-windows.ps1 +++ b/scripts/test-windows.ps1 @@ -166,6 +166,7 @@ $guards = @( "tests\windows\test_hook_augment.py", "tests\windows\test_ui_drive_listing.py", "tests\windows\test_cli_non_ascii_arg.py", + "tests\windows\test_mcp_stdio.py", "tests\windows\test_windows_update_handoff.py" ) diff --git a/tests/windows/mcp_stdio.py b/tests/windows/mcp_stdio.py index 218e9a27f..12487237d 100644 --- a/tests/windows/mcp_stdio.py +++ b/tests/windows/mcp_stdio.py @@ -148,6 +148,7 @@ def close(self): self.proc.kill() except Exception: pass + self._stderr_thread.join(timeout=10) def wait_projects_with_stats(server, timeout=90.0, poll=1.0): diff --git a/tests/windows/test_mcp_stdio.py b/tests/windows/test_mcp_stdio.py new file mode 100644 index 000000000..ced02842a --- /dev/null +++ b/tests/windows/test_mcp_stdio.py @@ -0,0 +1,59 @@ +"""Regression guard for clean Windows MCP stdio startup.""" + +import os +import subprocess +import sys +import tempfile + +from mcp_stdio import McpServer + + +def main(): + if os.name != "nt": + print("SKIP: Windows-only MCP stdio guard") + return 2 + + binary = sys.argv[1] if len(sys.argv) > 1 else os.environ.get("CBM_TEST_BINARY") + if not binary: + print("SETUP FAIL: CBM_TEST_BINARY is required", file=sys.stderr) + return 2 + + with tempfile.TemporaryDirectory(prefix="cbm-mcp-stdio-") as cache: + runtime = cache + "-runtime" + os.makedirs(runtime) + server = McpServer(binary, cache_dir=cache, extra_env={"CBM_RUNTIME_DIR": runtime}) + try: + with server: + server.initialize(timeout=30) + server.tools_list(timeout=30) + stderr = server.stderr_text() + finally: + stop = subprocess.run( + [binary, "daemon", "stop"], + env=server.env, + capture_output=True, + timeout=30, + ) + if stop.returncode != 0: + detail = (stop.stdout + stop.stderr).decode("utf-8", "replace") + raise RuntimeError( + "daemon cleanup failed (%d): %s" % (stop.returncode, detail.strip()) + ) + + forbidden = ( + "The system cannot find the path specified.", + "El sistema no puede encontrar la ruta especificada.", + ) + leaked = [message for message in forbidden if message in stderr] + if leaked: + print( + "FAIL: startup leaked an OS path error to stderr: " + ", ".join(leaked), + file=sys.stderr, + ) + return 1 + print("PASS: successful MCP startup emitted no OS path error") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())