diff --git a/cforge/commands/server/run.py b/cforge/commands/server/run.py index 23f1060..eec2243 100644 --- a/cforge/commands/server/run.py +++ b/cforge/commands/server/run.py @@ -173,8 +173,8 @@ def run( # Register if requested if register: - # Default to SSE if no protocol specified - is_sse = expose_sse or expose_streamable_http or (not expose_sse and not expose_streamable_http) + # Use streamable HTTP only when it's explicitly enabled without SSE + is_sse = expose_sse or not expose_streamable_http registered_server_id: Optional[str] = None try: diff --git a/tests/commands/server/test_run.py b/tests/commands/server/test_run.py index 815fc7b..17036a4 100644 --- a/tests/commands/server/test_run.py +++ b/tests/commands/server/test_run.py @@ -735,3 +735,96 @@ def test_run_temporary_without_source_uses_fallback_name(self) -> None: # Verify cleanup was registered mock_atexit.register.assert_called_once() mock_process.assert_called_once() + + def test_run_registration_with_streamable_http_only(self) -> None: + """Test that registration uses /mcp endpoint when only streamable HTTP is enabled.""" + with ( + patch("mcpgateway.translate.main") as mock_translate, + patch("multiprocessing.Process") as mock_process, + patch("cforge.commands.server.run.requests") as mock_requests, + patch("cforge.commands.server.run.make_authenticated_request") as mock_request, + ): + # Mock returning a 200 on health + mock_get_res = MagicMock() + mock_get_res.status_code = 200 + mock_requests.get = MagicMock(return_value=mock_get_res) + + mock_request.return_value = {"id": "streamable-http-server-id"} + + invoke_typer_command(run, stdio="uvx mcp-server-git", port=9005, expose_streamable_http=True, register=True) + + # Verify registration was attempted + mock_request.assert_called_once() + call_args = mock_request.call_args + json_data = call_args[1]["json_data"] + + # Verify correct endpoint and transport type for streamable HTTP + assert json_data["url"] == "http://127.0.0.1:9005/mcp" + assert json_data["transport"] == "STREAMABLEHTTP" + + # Verify translate_main was called via Process + mock_process.assert_called_once() + proc_call_args = mock_process.call_args[1] + assert proc_call_args.get("target") is mock_translate + + def test_run_registration_with_both_protocols_defaults_to_sse(self) -> None: + """Test that registration defaults to SSE when both protocols are enabled.""" + with ( + patch("mcpgateway.translate.main") as mock_translate, + patch("multiprocessing.Process") as mock_process, + patch("cforge.commands.server.run.requests") as mock_requests, + patch("cforge.commands.server.run.make_authenticated_request") as mock_request, + ): + # Mock returning a 200 on health + mock_get_res = MagicMock() + mock_get_res.status_code = 200 + mock_requests.get = MagicMock(return_value=mock_get_res) + + mock_request.return_value = {"id": "both-protocols-server-id"} + + invoke_typer_command(run, stdio="uvx mcp-server-git", port=9000, expose_sse=True, expose_streamable_http=True, register=True) + + # Verify registration was attempted + mock_request.assert_called_once() + call_args = mock_request.call_args + json_data = call_args[1]["json_data"] + + # Verify SSE is used when both protocols are enabled (SSE takes priority) + assert json_data["url"] == "http://127.0.0.1:9000/sse" + assert json_data["transport"] == "SSE" + + # Verify translate_main was called via Process + mock_process.assert_called_once() + proc_call_args = mock_process.call_args[1] + assert proc_call_args.get("target") is mock_translate + + def test_run_registration_without_protocol_flags_defaults_to_sse(self) -> None: + """Test that registration defaults to SSE when no protocol flags are specified.""" + with ( + patch("mcpgateway.translate.main") as mock_translate, + patch("multiprocessing.Process") as mock_process, + patch("cforge.commands.server.run.requests") as mock_requests, + patch("cforge.commands.server.run.make_authenticated_request") as mock_request, + ): + # Mock returning a 200 on health + mock_get_res = MagicMock() + mock_get_res.status_code = 200 + mock_requests.get = MagicMock(return_value=mock_get_res) + + mock_request.return_value = {"id": "default-server-id"} + + invoke_typer_command(run, stdio="uvx mcp-server-git", port=9000, register=True) + + # Verify registration was attempted + mock_request.assert_called_once() + call_args = mock_request.call_args + json_data = call_args[1]["json_data"] + + # Verify SSE is used by default when no protocol flags are specified + assert json_data["url"] == "http://127.0.0.1:9000/sse" + assert json_data["transport"] == "SSE" + + # Verify translate_main was called via Process + mock_process.assert_called_once() + proc_call_args = mock_process.call_args[1] + assert proc_call_args.get("target") is mock_translate