diff --git a/addon/FreecadRobustMCPBridge/freecad_mcp_bridge/server.py b/addon/FreecadRobustMCPBridge/freecad_mcp_bridge/server.py index d4fc242..c26cb0f 100644 --- a/addon/FreecadRobustMCPBridge/freecad_mcp_bridge/server.py +++ b/addon/FreecadRobustMCPBridge/freecad_mcp_bridge/server.py @@ -22,6 +22,7 @@ import contextlib import errno import io import json +import os import queue import sys import threading @@ -236,13 +237,14 @@ class FreecadMCPPlugin: self._running = True - # Print instance ID to stderr for test automation to capture. - # Stdout may be reserved for JSON-RPC when running in stdio mode. - print( - f"FREECAD_MCP_BRIDGE_INSTANCE_ID={self._instance_id}", - file=sys.stderr, - flush=True, - ) + # Print instance ID to stderr only for test automation (when env var is set). + # This avoids red error text in FreeCAD's console during normal use. + if os.environ.get("FREECAD_MCP_TESTING"): + print( + f"FREECAD_MCP_BRIDGE_INSTANCE_ID={self._instance_id}", + file=sys.stderr, + flush=True, + ) # Start the queue processing timer on the main thread self._start_queue_processor() diff --git a/addon/FreecadRobustMCPBridge/icons/mcp_start.svg b/addon/FreecadRobustMCPBridge/icons/mcp_start.svg index 9b4293b..8862453 100644 --- a/addon/FreecadRobustMCPBridge/icons/mcp_start.svg +++ b/addon/FreecadRobustMCPBridge/icons/mcp_start.svg @@ -1,54 +1,54 @@ - - + - + - + - + - - + + - + + - - + + - + - + - - + + - - + + - - + + - - - + + + - + - - + + - - + + - - - + + + diff --git a/addon/FreecadRobustMCPBridge/icons/mcp_status.svg b/addon/FreecadRobustMCPBridge/icons/mcp_status.svg index 563621e..6e4e76f 100644 --- a/addon/FreecadRobustMCPBridge/icons/mcp_status.svg +++ b/addon/FreecadRobustMCPBridge/icons/mcp_status.svg @@ -1,57 +1,57 @@ - - + - + - + - + - - + + - + + - - - - + + + + - + - - - + + + - - + + - - + + - - + + - - ? - ? + + ? + ? - + - + - + - - - + + + diff --git a/addon/FreecadRobustMCPBridge/icons/mcp_stop.svg b/addon/FreecadRobustMCPBridge/icons/mcp_stop.svg index 11071d4..d92b80b 100644 --- a/addon/FreecadRobustMCPBridge/icons/mcp_stop.svg +++ b/addon/FreecadRobustMCPBridge/icons/mcp_stop.svg @@ -1,43 +1,46 @@ - - + - - - + + + - + - - + + - + - - + + - - - + + + - - + + + + - + - + - + - - + + + + diff --git a/addon/FreecadRobustMCPBridge/icons/preferences-robust_mcp_bridge.svg b/addon/FreecadRobustMCPBridge/icons/preferences-robust_mcp_bridge.svg index 450a068..d99322e 100644 --- a/addon/FreecadRobustMCPBridge/icons/preferences-robust_mcp_bridge.svg +++ b/addon/FreecadRobustMCPBridge/icons/preferences-robust_mcp_bridge.svg @@ -1,84 +1,84 @@ - - + - + - + - + - - + + - + + - + - + - + - + - - - + + + - - + + - - + + - - + + - + - + - - - - + + + + - - - - + + + + - + - + - - - - + + + + - - - - + + + + - + - + - - + + - - + + - - - + + + diff --git a/src/freecad_mcp/server.py b/src/freecad_mcp/server.py index ac4ed19..5ce8ef3 100644 --- a/src/freecad_mcp/server.py +++ b/src/freecad_mcp/server.py @@ -33,6 +33,7 @@ Example: import argparse import logging +import os import sys import uuid from collections.abc import AsyncIterator @@ -411,9 +412,11 @@ def main() -> None: # Set up logging logging.getLogger().setLevel(config.log_level) - # Print instance ID to stderr for test automation to capture - # Must use stderr because stdout is reserved for JSON-RPC in stdio mode - print(f"FREECAD_MCP_INSTANCE_ID={INSTANCE_ID}", file=sys.stderr, flush=True) + # Print instance ID to stderr only for test automation (when env var is set). + # This avoids unwanted output during normal use. + # Must use stderr because stdout is reserved for JSON-RPC in stdio mode. + if os.environ.get("FREECAD_MCP_TESTING"): + print(f"FREECAD_MCP_INSTANCE_ID={INSTANCE_ID}", file=sys.stderr, flush=True) logger.info("Starting FreeCAD Robust MCP Server") logger.info("Instance ID: %s", INSTANCE_ID) diff --git a/tests/unit/test_server.py b/tests/unit/test_server.py index f52db4b..63bbf08 100644 --- a/tests/unit/test_server.py +++ b/tests/unit/test_server.py @@ -1,5 +1,6 @@ """Tests for the main server module.""" +import os import sys from unittest.mock import AsyncMock, MagicMock, patch @@ -226,7 +227,7 @@ class TestMain: """Tests for main function.""" def test_main_prints_instance_id(self): - """Main should print instance ID on startup.""" + """Main should print instance ID on startup when FREECAD_MCP_TESTING is set.""" import freecad_mcp.server as server_module from freecad_mcp.config import TransportType @@ -240,6 +241,7 @@ class TestMain: patch.object(server_module, "get_config", return_value=mock_config), patch.object(server_module.mcp, "run") as mock_run, patch("builtins.print") as mock_print, + patch.dict(os.environ, {"FREECAD_MCP_TESTING": "1"}), ): # Mock run to exit immediately mock_run.return_value = None @@ -258,6 +260,37 @@ class TestMain: ) assert instance_id_call.kwargs.get("file") == sys.stderr + def test_main_no_instance_id_without_testing_env(self): + """Main should NOT print instance ID when FREECAD_MCP_TESTING is unset.""" + import freecad_mcp.server as server_module + from freecad_mcp.config import TransportType + + mock_config = MagicMock() + mock_config.log_level = "INFO" + mock_config.mode = FreecadMode.EMBEDDED + mock_config.transport = TransportType.STDIO + + # Ensure FREECAD_MCP_TESTING is not set + env_without_testing = { + k: v for k, v in os.environ.items() if k != "FREECAD_MCP_TESTING" + } + + with ( + patch.object(sys, "argv", DEFAULT_ARGV), + patch.object(server_module, "get_config", return_value=mock_config), + patch.object(server_module.mcp, "run") as mock_run, + patch("builtins.print") as mock_print, + patch.dict(os.environ, env_without_testing, clear=True), + ): + # Mock run to exit immediately + mock_run.return_value = None + + server_module.main() + + # Check that instance ID was NOT printed + print_calls = [str(call) for call in mock_print.call_args_list] + assert not any("FREECAD_MCP_INSTANCE_ID=" in call for call in print_calls) + def test_main_http_transport(self): """Main should start HTTP transport when configured.""" import freecad_mcp.server as server_module @@ -438,6 +471,7 @@ class TestStdioProtocolCleanliness: "FREECAD_MODE": "xmlrpc", "FREECAD_XMLRPC_PORT": "59999", "FREECAD_SOCKET_HOST": "localhost", + "FREECAD_MCP_TESTING": "1", # Enable stderr instance ID output }, )