diff --git a/autogpt_platform/backend/backend/blocks/mcp/test_e2e.py b/autogpt_platform/backend/backend/blocks/mcp/test_e2e.py index d0bf0260a6..7818fac9ce 100644 --- a/autogpt_platform/backend/backend/blocks/mcp/test_e2e.py +++ b/autogpt_platform/backend/backend/blocks/mcp/test_e2e.py @@ -27,7 +27,7 @@ pytestmark = pytest.mark.skipif( class TestRealMCPServer: """Tests against the live OpenAI docs MCP server.""" - @pytest.mark.asyncio + @pytest.mark.asyncio(loop_scope="session") async def test_initialize(self): """Verify we can complete the MCP handshake with a real server.""" client = MCPClient(OPENAI_DOCS_MCP_URL) @@ -38,7 +38,7 @@ class TestRealMCPServer: assert result["serverInfo"]["name"] == "openai-docs-mcp" assert "tools" in result.get("capabilities", {}) - @pytest.mark.asyncio + @pytest.mark.asyncio(loop_scope="session") async def test_list_tools(self): """Verify we can discover tools from a real MCP server.""" client = MCPClient(OPENAI_DOCS_MCP_URL) @@ -58,7 +58,7 @@ class TestRealMCPServer: assert "query" in search_tool.input_schema.get("properties", {}) assert "query" in search_tool.input_schema.get("required", []) - @pytest.mark.asyncio + @pytest.mark.asyncio(loop_scope="session") async def test_call_tool_list_api_endpoints(self): """Call the list_api_endpoints tool and verify we get real data.""" client = MCPClient(OPENAI_DOCS_MCP_URL) @@ -75,7 +75,7 @@ class TestRealMCPServer: total = data.get("total", len(data.get("paths", []))) assert total > 50 - @pytest.mark.asyncio + @pytest.mark.asyncio(loop_scope="session") async def test_call_tool_search(self): """Search for docs and verify we get results.""" client = MCPClient(OPENAI_DOCS_MCP_URL) @@ -87,7 +87,7 @@ class TestRealMCPServer: assert not result.is_error assert len(result.content) >= 1 - @pytest.mark.asyncio + @pytest.mark.asyncio(loop_scope="session") async def test_sse_response_handling(self): """Verify the client correctly handles SSE responses from a real server. diff --git a/autogpt_platform/backend/backend/blocks/mcp/test_integration.py b/autogpt_platform/backend/backend/blocks/mcp/test_integration.py index 65839963e3..70658dbaaf 100644 --- a/autogpt_platform/backend/backend/blocks/mcp/test_integration.py +++ b/autogpt_platform/backend/backend/blocks/mcp/test_integration.py @@ -121,7 +121,7 @@ def _make_client(url: str, auth_token: str | None = None) -> MCPClient: class TestMCPClientIntegration: """Test MCPClient against a real local MCP server.""" - @pytest.mark.asyncio + @pytest.mark.asyncio(loop_scope="session") async def test_initialize(self, mcp_server): client = _make_client(mcp_server) result = await client.initialize() @@ -130,7 +130,7 @@ class TestMCPClientIntegration: assert result["serverInfo"]["name"] == "test-mcp-server" assert "tools" in result["capabilities"] - @pytest.mark.asyncio + @pytest.mark.asyncio(loop_scope="session") async def test_list_tools(self, mcp_server): client = _make_client(mcp_server) await client.initialize() @@ -152,7 +152,7 @@ class TestMCPClientIntegration: assert "a" in add.input_schema["properties"] assert "b" in add.input_schema["properties"] - @pytest.mark.asyncio + @pytest.mark.asyncio(loop_scope="session") async def test_call_tool_get_weather(self, mcp_server): client = _make_client(mcp_server) await client.initialize() @@ -167,7 +167,7 @@ class TestMCPClientIntegration: assert data["temperature"] == 22 assert data["condition"] == "sunny" - @pytest.mark.asyncio + @pytest.mark.asyncio(loop_scope="session") async def test_call_tool_add_numbers(self, mcp_server): client = _make_client(mcp_server) await client.initialize() @@ -177,7 +177,7 @@ class TestMCPClientIntegration: data = json.loads(result.content[0]["text"]) assert data["result"] == 10 - @pytest.mark.asyncio + @pytest.mark.asyncio(loop_scope="session") async def test_call_tool_echo(self, mcp_server): client = _make_client(mcp_server) await client.initialize() @@ -186,7 +186,7 @@ class TestMCPClientIntegration: assert not result.is_error assert result.content[0]["text"] == "Hello MCP!" - @pytest.mark.asyncio + @pytest.mark.asyncio(loop_scope="session") async def test_call_unknown_tool(self, mcp_server): client = _make_client(mcp_server) await client.initialize() @@ -195,7 +195,7 @@ class TestMCPClientIntegration: assert result.is_error assert "Unknown tool" in result.content[0]["text"] - @pytest.mark.asyncio + @pytest.mark.asyncio(loop_scope="session") async def test_auth_success(self, mcp_server_with_auth): url, token = mcp_server_with_auth client = _make_client(url, auth_token=token) @@ -206,7 +206,7 @@ class TestMCPClientIntegration: tools = await client.list_tools() assert len(tools) == 3 - @pytest.mark.asyncio + @pytest.mark.asyncio(loop_scope="session") async def test_auth_failure(self, mcp_server_with_auth): url, _ = mcp_server_with_auth client = _make_client(url, auth_token="wrong-token") @@ -214,7 +214,7 @@ class TestMCPClientIntegration: with pytest.raises(Exception): await client.initialize() - @pytest.mark.asyncio + @pytest.mark.asyncio(loop_scope="session") async def test_auth_missing(self, mcp_server_with_auth): url, _ = mcp_server_with_auth client = _make_client(url) @@ -229,7 +229,7 @@ class TestMCPClientIntegration: class TestMCPToolBlockIntegration: """Test MCPToolBlock end-to-end against a real local MCP server.""" - @pytest.mark.asyncio + @pytest.mark.asyncio(loop_scope="session") async def test_full_flow_get_weather(self, mcp_server): """Full flow: discover tools, select one, execute it.""" # Step 1: Discover tools (simulating what the frontend/API would do) @@ -261,7 +261,7 @@ class TestMCPToolBlockIntegration: assert result["temperature"] == 22 assert result["condition"] == "sunny" - @pytest.mark.asyncio + @pytest.mark.asyncio(loop_scope="session") async def test_full_flow_add_numbers(self, mcp_server): """Full flow for add_numbers tool.""" client = _make_client(mcp_server) @@ -285,7 +285,7 @@ class TestMCPToolBlockIntegration: assert outputs[0][0] == "result" assert outputs[0][1]["result"] == 100 - @pytest.mark.asyncio + @pytest.mark.asyncio(loop_scope="session") async def test_full_flow_echo_plain_text(self, mcp_server): """Verify plain text (non-JSON) responses work.""" block = MCPToolBlock() @@ -308,7 +308,7 @@ class TestMCPToolBlockIntegration: assert outputs[0][0] == "result" assert outputs[0][1] == "Hello from AutoGPT!" - @pytest.mark.asyncio + @pytest.mark.asyncio(loop_scope="session") async def test_full_flow_unknown_tool_yields_error(self, mcp_server): """Calling an unknown tool should yield an error output.""" block = MCPToolBlock() @@ -326,7 +326,7 @@ class TestMCPToolBlockIntegration: assert outputs[0][0] == "error" assert "returned an error" in outputs[0][1] - @pytest.mark.asyncio + @pytest.mark.asyncio(loop_scope="session") async def test_full_flow_with_auth(self, mcp_server_with_auth): """Full flow with authentication via credentials kwarg.""" url, token = mcp_server_with_auth @@ -363,7 +363,7 @@ class TestMCPToolBlockIntegration: assert outputs[0][0] == "result" assert outputs[0][1] == "Authenticated!" - @pytest.mark.asyncio + @pytest.mark.asyncio(loop_scope="session") async def test_no_credentials_runs_without_auth(self, mcp_server): """Block runs without auth when no credentials are provided.""" block = MCPToolBlock() diff --git a/autogpt_platform/backend/backend/blocks/mcp/test_mcp.py b/autogpt_platform/backend/backend/blocks/mcp/test_mcp.py index 451f417513..8cb49b0fee 100644 --- a/autogpt_platform/backend/backend/blocks/mcp/test_mcp.py +++ b/autogpt_platform/backend/backend/blocks/mcp/test_mcp.py @@ -123,7 +123,7 @@ class TestMCPClient: client = MCPClient("https://mcp.example.com/mcp/") assert client.server_url == "https://mcp.example.com/mcp" - @pytest.mark.asyncio + @pytest.mark.asyncio(loop_scope="session") async def test_send_request_success(self): client = MCPClient("https://mcp.example.com") @@ -138,7 +138,7 @@ class TestMCPClient: result = await client._send_request("tools/list") assert result == {"tools": []} - @pytest.mark.asyncio + @pytest.mark.asyncio(loop_scope="session") async def test_send_request_error(self): client = MCPClient("https://mcp.example.com") @@ -149,7 +149,7 @@ class TestMCPClient: with pytest.raises(MCPClientError, match="Invalid Request"): await client._send_request("tools/list") - @pytest.mark.asyncio + @pytest.mark.asyncio(loop_scope="session") async def test_list_tools(self): client = MCPClient("https://mcp.example.com") @@ -185,7 +185,7 @@ class TestMCPClient: assert tools[0].input_schema["properties"]["city"]["type"] == "string" assert tools[1].name == "search" - @pytest.mark.asyncio + @pytest.mark.asyncio(loop_scope="session") async def test_list_tools_empty(self): client = MCPClient("https://mcp.example.com") @@ -194,7 +194,7 @@ class TestMCPClient: assert tools == [] - @pytest.mark.asyncio + @pytest.mark.asyncio(loop_scope="session") async def test_list_tools_none_result(self): client = MCPClient("https://mcp.example.com") @@ -203,7 +203,7 @@ class TestMCPClient: assert tools == [] - @pytest.mark.asyncio + @pytest.mark.asyncio(loop_scope="session") async def test_call_tool_success(self): client = MCPClient("https://mcp.example.com") @@ -221,7 +221,7 @@ class TestMCPClient: assert len(result.content) == 1 assert result.content[0]["type"] == "text" - @pytest.mark.asyncio + @pytest.mark.asyncio(loop_scope="session") async def test_call_tool_error(self): client = MCPClient("https://mcp.example.com") @@ -235,7 +235,7 @@ class TestMCPClient: assert result.is_error - @pytest.mark.asyncio + @pytest.mark.asyncio(loop_scope="session") async def test_call_tool_none_result(self): client = MCPClient("https://mcp.example.com") @@ -244,7 +244,7 @@ class TestMCPClient: assert result.is_error - @pytest.mark.asyncio + @pytest.mark.asyncio(loop_scope="session") async def test_initialize(self): client = MCPClient("https://mcp.example.com") @@ -340,13 +340,13 @@ class TestMCPToolBlock: missing = MCPToolBlock.Input.get_missing_input(data) assert missing == set() - @pytest.mark.asyncio + @pytest.mark.asyncio(loop_scope="session") async def test_run_with_mock(self): """Test the block using the built-in test infrastructure.""" block = MCPToolBlock() await execute_block_test(block) - @pytest.mark.asyncio + @pytest.mark.asyncio(loop_scope="session") async def test_run_missing_server_url(self): block = MCPToolBlock() input_data = MCPToolBlock.Input( @@ -358,7 +358,7 @@ class TestMCPToolBlock: outputs.append((name, data)) assert outputs == [("error", "MCP server URL is required")] - @pytest.mark.asyncio + @pytest.mark.asyncio(loop_scope="session") async def test_run_missing_tool(self): block = MCPToolBlock() input_data = MCPToolBlock.Input( @@ -372,7 +372,7 @@ class TestMCPToolBlock: ("error", "No tool selected. Please select a tool from the dropdown.") ] - @pytest.mark.asyncio + @pytest.mark.asyncio(loop_scope="session") async def test_run_success(self): block = MCPToolBlock() input_data = MCPToolBlock.Input( @@ -398,7 +398,7 @@ class TestMCPToolBlock: assert outputs[0][0] == "result" assert outputs[0][1] == {"temp": 20, "city": "London"} - @pytest.mark.asyncio + @pytest.mark.asyncio(loop_scope="session") async def test_run_mcp_error(self): block = MCPToolBlock() input_data = MCPToolBlock.Input( @@ -418,7 +418,7 @@ class TestMCPToolBlock: assert outputs[0][0] == "error" assert "Tool not found" in outputs[0][1] - @pytest.mark.asyncio + @pytest.mark.asyncio(loop_scope="session") async def test_call_mcp_tool_parses_json_text(self): block = MCPToolBlock() @@ -445,7 +445,7 @@ class TestMCPToolBlock: assert result == {"temp": 20} - @pytest.mark.asyncio + @pytest.mark.asyncio(loop_scope="session") async def test_call_mcp_tool_plain_text(self): block = MCPToolBlock() @@ -472,7 +472,7 @@ class TestMCPToolBlock: assert result == "Hello, world!" - @pytest.mark.asyncio + @pytest.mark.asyncio(loop_scope="session") async def test_call_mcp_tool_multiple_content(self): block = MCPToolBlock() @@ -500,7 +500,7 @@ class TestMCPToolBlock: assert result == ["Part 1", {"part": 2}] - @pytest.mark.asyncio + @pytest.mark.asyncio(loop_scope="session") async def test_call_mcp_tool_error_result(self): block = MCPToolBlock() @@ -522,7 +522,7 @@ class TestMCPToolBlock: with pytest.raises(MCPClientError, match="returned an error"): await block._call_mcp_tool("https://mcp.example.com", "test_tool", {}) - @pytest.mark.asyncio + @pytest.mark.asyncio(loop_scope="session") async def test_call_mcp_tool_image_content(self): block = MCPToolBlock() @@ -557,7 +557,7 @@ class TestMCPToolBlock: "mimeType": "image/png", } - @pytest.mark.asyncio + @pytest.mark.asyncio(loop_scope="session") async def test_run_with_credentials(self): """Verify the block uses OAuth2Credentials and passes auth token.""" from pydantic import SecretStr @@ -594,7 +594,7 @@ class TestMCPToolBlock: assert captured_tokens == ["resolved-token"] - @pytest.mark.asyncio + @pytest.mark.asyncio(loop_scope="session") async def test_run_without_credentials(self): """Verify the block works without credentials (public server).""" block = MCPToolBlock() diff --git a/autogpt_platform/backend/backend/blocks/mcp/test_oauth.py b/autogpt_platform/backend/backend/blocks/mcp/test_oauth.py index 315e9c1b60..e9a42f68ea 100644 --- a/autogpt_platform/backend/backend/blocks/mcp/test_oauth.py +++ b/autogpt_platform/backend/backend/blocks/mcp/test_oauth.py @@ -66,7 +66,7 @@ class TestMCPOAuthHandler: assert "code_challenge" not in url assert "code_challenge_method" not in url - @pytest.mark.asyncio + @pytest.mark.asyncio(loop_scope="session") async def test_exchange_code_for_tokens(self): handler = self._make_handler() @@ -96,7 +96,7 @@ class TestMCPOAuthHandler: assert creds.scopes == ["read"] assert creds.access_token_expires_at is not None - @pytest.mark.asyncio + @pytest.mark.asyncio(loop_scope="session") async def test_refresh_tokens(self): handler = self._make_handler() @@ -128,7 +128,7 @@ class TestMCPOAuthHandler: assert refreshed.refresh_token is not None assert refreshed.refresh_token.get_secret_value() == "new-refresh" - @pytest.mark.asyncio + @pytest.mark.asyncio(loop_scope="session") async def test_refresh_tokens_no_refresh_token(self): handler = self._make_handler() @@ -142,7 +142,7 @@ class TestMCPOAuthHandler: with pytest.raises(ValueError, match="No refresh token"): await handler._refresh_tokens(creds) - @pytest.mark.asyncio + @pytest.mark.asyncio(loop_scope="session") async def test_revoke_tokens_no_url(self): handler = self._make_handler(revoke_url=None) @@ -156,7 +156,7 @@ class TestMCPOAuthHandler: result = await handler.revoke_tokens(creds) assert result is False - @pytest.mark.asyncio + @pytest.mark.asyncio(loop_scope="session") async def test_revoke_tokens_with_url(self): handler = self._make_handler(revoke_url="https://auth.example.com/revoke") @@ -181,7 +181,7 @@ class TestMCPOAuthHandler: class TestMCPClientDiscovery: """Tests for MCPClient OAuth metadata discovery.""" - @pytest.mark.asyncio + @pytest.mark.asyncio(loop_scope="session") async def test_discover_auth_found(self): client = MCPClient("https://mcp.example.com/mcp") @@ -201,7 +201,7 @@ class TestMCPClientDiscovery: assert result is not None assert result["authorization_servers"] == ["https://auth.example.com"] - @pytest.mark.asyncio + @pytest.mark.asyncio(loop_scope="session") async def test_discover_auth_not_found(self): client = MCPClient("https://mcp.example.com/mcp") @@ -215,7 +215,7 @@ class TestMCPClientDiscovery: assert result is None - @pytest.mark.asyncio + @pytest.mark.asyncio(loop_scope="session") async def test_discover_auth_server_metadata(self): client = MCPClient("https://mcp.example.com/mcp")