diff --git a/.github/workflows/pr-test-rust.yml b/.github/workflows/pr-test-rust.yml index b944bc516..1f1273d3b 100644 --- a/.github/workflows/pr-test-rust.yml +++ b/.github/workflows/pr-test-rust.yml @@ -259,6 +259,17 @@ jobs: echo "ATP_PASSWORD=oracle" >> $GITHUB_ENV echo "ATP_DSN=localhost:1521/XEPDB1" >> $GITHUB_ENV + - name: Start Brave MCP Server + run: | + docker run -d --rm \ + -p 8001:8080 \ + -e BRAVE_API_KEY \ + --name brave-search-server \ + shoofio/brave-search-mcp-sse:1.0.10 + echo "Starting Brave MCP Server..." + sleep 2 + curl -f --max-time 1 http://localhost:8001/sse > /dev/null 2>&1 && echo "Brave MCP Server is healthy!" || echo "Brave MCP Server responded" + - name: Build python binding run: | source "$HOME/.cargo/env" @@ -284,6 +295,12 @@ jobs: source "$HOME/.cargo/env" SHOW_ROUTER_LOGS=1 ROUTER_LOCAL_MODEL_PATH="/home/ubuntu/models" pytest py_test/e2e_grpc -s -vv -o log_cli=true --log-cli-level=INFO + - name: Cleanup Brave MCP Server + if: always() + run: | + docker stop brave-search-server || true + docker rm brave-search-server || true + - name: Cleanup Oracle Database if: always() run: | diff --git a/sgl-router/py_test/e2e_response_api/backends/test_grpc_backend.py b/sgl-router/py_test/e2e_response_api/backends/test_grpc_backend.py index 80b952469..4070aff7b 100644 --- a/sgl-router/py_test/e2e_response_api/backends/test_grpc_backend.py +++ b/sgl-router/py_test/e2e_response_api/backends/test_grpc_backend.py @@ -136,14 +136,6 @@ class TestGrpcBackend(StateManagementTests, MCPTests, StructuredOutputBaseTest): self.assertIsInstance(output_json["answer"], str) self.assertTrue(output_json["answer"], "Answer is empty") - @unittest.skip("TODO: Temporary skip since deepwiki might hit rate limit") - def test_mcp_basic_tool_call(self): - return super().test_mcp_basic_tool_call() - - @unittest.skip("Temporary skip since deepwiki might hit rate limit") - def test_mcp_basic_tool_call_streaming(self): - return super().test_mcp_basic_tool_call_streaming() - class TestGrpcHarmonyBackend( StateManagementTests, MCPTests, FunctionCallingBaseTest, StructuredOutputBaseTest diff --git a/sgl-router/py_test/e2e_response_api/backends/test_http_backend.py b/sgl-router/py_test/e2e_response_api/backends/test_http_backend.py index 3f97fe915..e5131e6d5 100644 --- a/sgl-router/py_test/e2e_response_api/backends/test_http_backend.py +++ b/sgl-router/py_test/e2e_response_api/backends/test_http_backend.py @@ -75,14 +75,6 @@ class TestOpenaiBackend( def test_mixed_mcp_and_function_tools_streaming(self): super().test_mixed_mcp_and_function_tools_streaming() - @unittest.skip("Temporary skip since deepwiki might hit rate limit") - def test_mcp_basic_tool_call(self): - super().test_mcp_basic_tool_call() - - @unittest.skip("Temporary skip since deepwiki might hit rate limit") - def test_mcp_basic_tool_call_streaming(self): - super().test_mcp_basic_tool_call_streaming() - class TestXaiBackend(StateManagementTests): """End to end tests for XAI backend.""" diff --git a/sgl-router/py_test/e2e_response_api/mixins/mcp.py b/sgl-router/py_test/e2e_response_api/mixins/mcp.py index 2f418d816..cf7fe31b4 100644 --- a/sgl-router/py_test/e2e_response_api/mixins/mcp.py +++ b/sgl-router/py_test/e2e_response_api/mixins/mcp.py @@ -17,25 +17,42 @@ class MCPTests(ResponseAPIBaseTest): # Subclasses can override this to enable strict validation mcp_validation_mode = "relaxed" + # Shared constants for MCP tests + BRAVE_MCP_TOOL = { + "type": "mcp", + "server_label": "brave", + "server_description": "A Tool to do web search", + "server_url": "http://localhost:8001/sse", + "require_approval": "never", + } + + MCP_TEST_PROMPT = ( + "show me some news about sglang router, use the tool to just search " + "one result and return one sentence response" + ) + + GET_WEATHER_FUNCTION = { + "type": "function", + "name": "get_weather", + "description": "Get the current weather in a given location", + "parameters": { + "type": "object", + "properties": {"location": {"type": "string"}}, + "required": ["location"], + }, + } + def test_mcp_basic_tool_call(self): """Test basic MCP tool call (non-streaming). Validation strictness is controlled by the class attribute `mcp_validation_mode`. Set to "strict" in subclasses for additional HTTP-specific validation. """ - tools = [ - { - "type": "mcp", - "server_label": "deepwiki", - "server_url": "https://mcp.deepwiki.com/mcp", - "require_approval": "never", - } - ] - resp = self.create_response( - "What transport protocols does the 2025-03-26 version of the MCP spec (modelcontextprotocol/modelcontextprotocol) support?", - tools=tools, + self.MCP_TEST_PROMPT, + tools=[self.BRAVE_MCP_TOOL], stream=False, + reasoning={"effort": "low"}, ) # Should successfully make the request @@ -75,7 +92,7 @@ class MCPTests(ResponseAPIBaseTest): self.assertIn("status", mcp_call) self.assertEqual(mcp_call["status"], "completed") self.assertIn("server_label", mcp_call) - self.assertEqual(mcp_call["server_label"], "deepwiki") + self.assertEqual(mcp_call["server_label"], "brave") self.assertIn("name", mcp_call) self.assertIn("arguments", mcp_call) self.assertIn("output", mcp_call) @@ -105,19 +122,11 @@ class MCPTests(ResponseAPIBaseTest): Validation strictness is controlled by the class attribute `mcp_validation_mode`. Set to "strict" in subclasses for additional HTTP-specific validation. """ - tools = [ - { - "type": "mcp", - "server_label": "deepwiki", - "server_url": "https://mcp.deepwiki.com/mcp", - "require_approval": "never", - } - ] - resp = self.create_response( - "What transport protocols does the 2025-03-26 version of the MCP spec (modelcontextprotocol/modelcontextprotocol) support?", - tools=tools, + self.MCP_TEST_PROMPT, + tools=[self.BRAVE_MCP_TOOL], stream=True, + reasoning={"effort": "low"}, ) # Should successfully make the request @@ -197,7 +206,7 @@ class MCPTests(ResponseAPIBaseTest): for mcp_call in mcp_calls: self.assertEqual(mcp_call.get("status"), "completed") - self.assertEqual(mcp_call.get("server_label"), "deepwiki") + self.assertEqual(mcp_call.get("server_label"), "brave") self.assertIn("name", mcp_call) self.assertIn("arguments", mcp_call) self.assertIn("output", mcp_call) @@ -247,28 +256,9 @@ class MCPTests(ResponseAPIBaseTest): def test_mixed_mcp_and_function_tools(self): """Test mixed MCP and function tools (non-streaming).""" - tools = [ - { - "type": "mcp", - "server_url": "https://mcp.deepwiki.com/mcp", - "server_label": "deepwiki", - "require_approval": "never", - }, - { - "type": "function", - "name": "get_weather", - "description": "Get the current weather in a given location", - "parameters": { - "type": "object", - "properties": {"location": {"type": "string"}}, - "required": ["location"], - }, - }, - ] - resp = self.create_response( "What is the weather in seattle now?", - tools=tools, + tools=[self.BRAVE_MCP_TOOL, self.GET_WEATHER_FUNCTION], stream=False, tool_choice="auto", ) @@ -311,28 +301,9 @@ class MCPTests(ResponseAPIBaseTest): def test_mixed_mcp_and_function_tools_streaming(self): """Test mixed MCP and function tools (streaming).""" - tools = [ - { - "type": "mcp", - "server_url": "https://mcp.deepwiki.com/mcp", - "server_label": "deepwiki", - "require_approval": "never", - }, - { - "type": "function", - "name": "get_weather", - "description": "Get the current weather in a given location", - "parameters": { - "type": "object", - "properties": {"location": {"type": "string"}}, - "required": ["location"], - }, - }, - ] - resp = self.create_response( "What is the weather in seattle now?", - tools=tools, + tools=[self.BRAVE_MCP_TOOL, self.GET_WEATHER_FUNCTION], stream=True, tool_choice="auto", # Encourage tool usage )