Skip to main content
Glama
test_integration.py21 kB
"""Integration tests for complete mcp-test-mcp workflows. This module tests end-to-end workflows through the mcp-test-mcp tools, verifying that connections, tools, resources, and prompts work together correctly from connection to disconnection. """ from datetime import datetime from unittest.mock import AsyncMock, MagicMock, patch import pytest from mcp.types import Tool as McpTool from mcp_test_mcp.connection import ConnectionManager from mcp_test_mcp.models import ConnectionState from mcp_test_mcp.tools.connection import ( connect_to_server, disconnect, get_connection_status, ) from mcp_test_mcp.tools.prompts import get_prompt, list_prompts from mcp_test_mcp.tools.resources import list_resources, read_resource from mcp_test_mcp.tools.tools import call_tool, list_tools @pytest.fixture(autouse=True) async def cleanup_connection(): """Ensure connection is cleaned up before and after each test.""" # Cleanup before test await ConnectionManager.disconnect() yield # Cleanup after test await ConnectionManager.disconnect() @pytest.fixture def mock_connection_state(): """Create a mock ConnectionState for testing.""" return ConnectionState( server_url="http://test.example.com/mcp", transport="streamable-http", connected_at=datetime.now(), server_info={"name": "test-server", "version": "1.0.0"}, statistics={ "tools_called": 0, "resources_accessed": 0, "prompts_executed": 0, "errors": 0, }, ) class TestToolWorkflows: """Integration tests for complete tool workflows.""" @pytest.mark.asyncio async def test_full_tool_workflow(self, mock_connection_state): """Test complete connect → list_tools → call_tool → disconnect workflow.""" # Create mock client mock_client = MagicMock() mock_client.__aenter__ = AsyncMock(return_value=mock_client) mock_client.__aexit__ = AsyncMock() mock_client.is_connected = MagicMock(return_value=True) # Mock session with server info mock_session = MagicMock() mock_session.server_info.name = "test-server" mock_session.server_info.version = "1.0.0" mock_session.server_capabilities.tools = {"listChanged": True} mock_session.server_capabilities.resources = {"subscribe": True} mock_session.server_capabilities.prompts = {"listChanged": True} mock_client._session = mock_session # Mock list_tools response tool1 = MagicMock(spec=McpTool) tool1.name = "add" tool1.description = "Add two numbers" tool1.inputSchema = MagicMock() tool1.inputSchema.model_dump.return_value = { "type": "object", "properties": { "a": {"type": "number"}, "b": {"type": "number"}, }, "required": ["a", "b"], } tools_result = MagicMock() tools_result.tools = [tool1] mock_client.list_tools = AsyncMock(return_value=tools_result) # Mock call_tool response tool_result = MagicMock() content_item = MagicMock() content_item.text = "8" tool_result.content = [content_item] mock_client.call_tool = AsyncMock(return_value=tool_result) with patch("mcp_test_mcp.connection.Client", return_value=mock_client): # Step 1: Connect connect_result = await connect_to_server("http://test.example.com/mcp") assert connect_result["success"] is True assert connect_result["connection"]["server_url"] == "http://test.example.com/mcp" # Step 2: List tools tools_result = await list_tools() assert tools_result["success"] is True assert len(tools_result["tools"]) == 1 assert tools_result["tools"][0]["name"] == "add" assert "input_schema" in tools_result["tools"][0] # Step 3: Call tool call_result = await call_tool("add", {"a": 5, "b": 3}) assert call_result["success"] is True assert call_result["tool_call"]["tool_name"] == "add" assert call_result["tool_call"]["result"] == "8" assert "execution" in call_result["tool_call"] assert "duration_ms" in call_result["tool_call"]["execution"] # Step 4: Verify connection status status_result = await get_connection_status() assert status_result["connected"] is True # Only call_tool increments tools_called, not list_tools assert status_result["connection"]["statistics"]["tools_called"] == 1 # Step 5: Disconnect disconnect_result = await disconnect() assert disconnect_result["success"] is True # Step 6: Verify disconnected final_status = await get_connection_status() assert final_status["connected"] is False @pytest.mark.asyncio async def test_tool_error_recovery(self): """Test error recovery when calling tools.""" mock_client = MagicMock() mock_client.__aenter__ = AsyncMock(return_value=mock_client) mock_client.__aexit__ = AsyncMock() mock_client.is_connected = MagicMock(return_value=True) mock_session = MagicMock() mock_session.server_info.name = "test-server" mock_session.server_info.version = "1.0.0" mock_session.server_capabilities.tools = {"listChanged": True} mock_session.server_capabilities.resources = {"subscribe": True} mock_session.server_capabilities.prompts = {"listChanged": True} mock_client._session = mock_session # First call fails, second succeeds error_result = MagicMock() mock_client.call_tool = AsyncMock(side_effect=[ Exception("Cannot divide by zero"), MagicMock(content=[MagicMock(text="3")]), ]) with patch("mcp_test_mcp.connection.Client", return_value=mock_client): await connect_to_server("http://test.example.com/mcp") # Call that fails error_result = await call_tool("divide", {"a": 10, "b": 0}) assert error_result["success"] is False assert error_result["error"]["error_type"] in ["execution_error", "tool_execution_failed"] # Verify error was counted status = await get_connection_status() assert status["connection"]["statistics"]["errors"] == 1 # Call that succeeds success_result = await call_tool("add", {"a": 1, "b": 2}) assert success_result["success"] is True await disconnect() class TestResourceWorkflows: """Integration tests for complete resource workflows.""" @pytest.mark.asyncio async def test_full_resource_workflow(self): """Test complete connect → list_resources → read_resource → disconnect workflow.""" mock_client = MagicMock() mock_client.__aenter__ = AsyncMock(return_value=mock_client) mock_client.__aexit__ = AsyncMock() mock_client.is_connected = MagicMock(return_value=True) mock_session = MagicMock() mock_session.server_info.name = "test-server" mock_session.server_info.version = "1.0.0" mock_session.server_capabilities.tools = {"listChanged": True} mock_session.server_capabilities.resources = {"subscribe": True} mock_session.server_capabilities.prompts = {"listChanged": True} mock_client._session = mock_session # Mock list_resources resource1 = MagicMock() resource1.uri = "config://settings" resource1.name = "Settings" resource1.mimeType = "application/json" resource1.description = "App settings" resources_result = MagicMock() resources_result.resources = [resource1] mock_client.list_resources = AsyncMock(return_value=resources_result) # Mock read_resource content_item = MagicMock() content_item.text = '{"theme": "dark"}' content_item.mimeType = "application/json" read_result = MagicMock() read_result.contents = [content_item] mock_client.read_resource = AsyncMock(return_value=read_result) with patch("mcp_test_mcp.connection.Client", return_value=mock_client): # Connect await connect_to_server("http://test.example.com/mcp") # List resources list_result = await list_resources() assert list_result["success"] is True assert len(list_result["resources"]) == 1 assert list_result["resources"][0]["uri"] == "config://settings" # Read resource read_result = await read_resource("config://settings") assert read_result["success"] is True assert read_result["resource"]["uri"] == "config://settings" assert read_result["resource"]["content"] is not None # Verify statistics status = await get_connection_status() # Only read_resource increments counter, not list_resources assert status["connection"]["statistics"]["resources_accessed"] == 1 await disconnect() @pytest.mark.asyncio async def test_resource_multiple_reads(self): """Test reading multiple resources in sequence.""" mock_client = MagicMock() mock_client.__aenter__ = AsyncMock(return_value=mock_client) mock_client.__aexit__ = AsyncMock() mock_client.is_connected = MagicMock(return_value=True) mock_session = MagicMock() mock_session.server_info.name = "test-server" mock_session.server_info.version = "1.0.0" mock_session.server_capabilities.tools = {"listChanged": True} mock_session.server_capabilities.resources = {"subscribe": True} mock_session.server_capabilities.prompts = {"listChanged": True} mock_client._session = mock_session # Mock read_resource content_item = MagicMock() content_item.text = '{"data": "test"}' content_item.mimeType = "application/json" read_result = MagicMock() read_result.contents = [content_item] mock_client.read_resource = AsyncMock(return_value=read_result) with patch("mcp_test_mcp.connection.Client", return_value=mock_client): await connect_to_server("http://test.example.com/mcp") # Read multiple resources result1 = await read_resource("config://settings") assert result1["success"] is True result2 = await read_resource("data://users") assert result2["success"] is True # Verify statistics accumulated status = await get_connection_status() assert status["connection"]["statistics"]["resources_accessed"] == 2 await disconnect() class TestPromptWorkflows: """Integration tests for complete prompt workflows.""" @pytest.mark.asyncio async def test_full_prompt_workflow(self): """Test complete connect → list_prompts → get_prompt → disconnect workflow.""" mock_client = MagicMock() mock_client.__aenter__ = AsyncMock(return_value=mock_client) mock_client.__aexit__ = AsyncMock() mock_client.is_connected = MagicMock(return_value=True) mock_session = MagicMock() mock_session.server_info.name = "test-server" mock_session.server_info.version = "1.0.0" mock_session.server_capabilities.tools = {"listChanged": True} mock_session.server_capabilities.resources = {"subscribe": True} mock_session.server_capabilities.prompts = {"listChanged": True} mock_client._session = mock_session # Mock list_prompts prompt1 = MagicMock() prompt1.name = "greeting" prompt1.description = "Generate a greeting" prompt1.arguments = [] prompts_result = MagicMock() prompts_result.prompts = [prompt1] mock_client.list_prompts = AsyncMock(return_value=prompts_result) # Mock get_prompt message = MagicMock() message.role = "user" message.content.text = "Hello, Alice!" get_result = MagicMock() get_result.messages = [message] mock_client.get_prompt = AsyncMock(return_value=get_result) with patch("mcp_test_mcp.connection.Client", return_value=mock_client): await connect_to_server("http://test.example.com/mcp") # List prompts list_result = await list_prompts() assert list_result["success"] is True assert len(list_result["prompts"]) == 1 assert list_result["prompts"][0]["name"] == "greeting" # Get prompt get_result = await get_prompt("greeting", {"name": "Alice"}) assert get_result["success"] is True assert get_result["prompt"]["name"] == "greeting" # Verify statistics status = await get_connection_status() # Only get_prompt increments counter, not list_prompts assert status["connection"]["statistics"]["prompts_executed"] == 1 await disconnect() class TestMixedWorkflows: """Integration tests for workflows using multiple capabilities.""" @pytest.mark.asyncio async def test_mixed_operations_workflow(self): """Test workflow using tools, resources, and prompts together.""" mock_client = MagicMock() mock_client.__aenter__ = AsyncMock(return_value=mock_client) mock_client.__aexit__ = AsyncMock() mock_client.is_connected = MagicMock(return_value=True) mock_session = MagicMock() mock_session.server_info.name = "test-server" mock_session.server_info.version = "1.0.0" mock_session.server_capabilities.tools = {"listChanged": True} mock_session.server_capabilities.resources = {"subscribe": True} mock_session.server_capabilities.prompts = {"listChanged": True} mock_client._session = mock_session # Setup all mocks tools_result = MagicMock() tools_result.tools = [] mock_client.list_tools = AsyncMock(return_value=tools_result) tool_result = MagicMock() tool_result.content = [MagicMock(text="20")] mock_client.call_tool = AsyncMock(return_value=tool_result) resources_result = MagicMock() resources_result.resources = [] mock_client.list_resources = AsyncMock(return_value=resources_result) read_result = MagicMock() read_result.contents = [MagicMock(text='{}', mimeType="application/json")] mock_client.read_resource = AsyncMock(return_value=read_result) prompts_result = MagicMock() prompts_result.prompts = [] mock_client.list_prompts = AsyncMock(return_value=prompts_result) get_prompt_result = MagicMock() get_prompt_result.messages = [MagicMock(role="user", content=MagicMock(text="Hi"))] mock_client.get_prompt = AsyncMock(return_value=get_prompt_result) with patch("mcp_test_mcp.connection.Client", return_value=mock_client): await connect_to_server("http://test.example.com/mcp") # Use tools await list_tools() await call_tool("multiply", {"a": 4, "b": 5}) # Use resources await list_resources() await read_resource("config://settings") # Use prompts await list_prompts() await get_prompt("greeting", {"name": "Bob"}) # Verify all statistics accumulated final_status = await get_connection_status() # Only actual operations increment counters, not list operations assert final_status["connection"]["statistics"]["tools_called"] == 1 # call_tool only assert final_status["connection"]["statistics"]["resources_accessed"] == 1 # read_resource only assert final_status["connection"]["statistics"]["prompts_executed"] == 1 # get_prompt only await disconnect() @pytest.mark.asyncio async def test_state_consistency_across_operations(self): """Test that connection state remains consistent throughout operations.""" mock_client = MagicMock() mock_client.__aenter__ = AsyncMock(return_value=mock_client) mock_client.__aexit__ = AsyncMock() mock_client.is_connected = MagicMock(return_value=True) mock_session = MagicMock() mock_session.server_info.name = "test-server" mock_session.server_info.version = "1.0.0" mock_session.server_capabilities.tools = {"listChanged": True} mock_session.server_capabilities.resources = {"subscribe": True} mock_session.server_capabilities.prompts = {"listChanged": True} mock_client._session = mock_session tool_result = MagicMock() tool_result.content = [MagicMock(text="result")] mock_client.call_tool = AsyncMock(return_value=tool_result) with patch("mcp_test_mcp.connection.Client", return_value=mock_client): # Connect connect_result = await connect_to_server("http://test.example.com/mcp") initial_url = connect_result["connection"]["server_url"] # Perform multiple operations for i in range(5): result = await call_tool("add", {"a": i, "b": i}) assert result["success"] is True # Final status check final_status = await get_connection_status() assert final_status["connection"]["server_url"] == initial_url assert final_status["connection"]["statistics"]["tools_called"] == 5 await disconnect() class TestConnectionErrors: """Integration tests for connection error scenarios.""" @pytest.mark.asyncio async def test_operations_without_connection(self): """Test that operations fail gracefully when not connected.""" # Ensure no connection await disconnect() # Try various operations result = await list_tools() assert result["success"] is False assert result["error"]["error_type"] == "not_connected" result = await call_tool("add", {"a": 1, "b": 2}) assert result["success"] is False assert result["error"]["error_type"] == "not_connected" result = await list_resources() assert result["success"] is False assert result["error"]["error_type"] == "not_connected" result = await read_resource("config://settings") assert result["success"] is False assert result["error"]["error_type"] == "not_connected" result = await list_prompts() assert result["success"] is False assert result["error"]["error_type"] == "not_connected" result = await get_prompt("greeting", {"name": "Test"}) assert result["success"] is False assert result["error"]["error_type"] == "not_connected" @pytest.mark.asyncio async def test_reconnect_workflow(self): """Test connecting, disconnecting, and reconnecting.""" mock_client = MagicMock() mock_client.__aenter__ = AsyncMock(return_value=mock_client) mock_client.__aexit__ = AsyncMock() mock_client.is_connected = MagicMock(return_value=True) mock_session = MagicMock() mock_session.server_info.name = "test-server" mock_session.server_info.version = "1.0.0" mock_session.server_capabilities.tools = {"listChanged": True} mock_session.server_capabilities.resources = {"subscribe": True} mock_session.server_capabilities.prompts = {"listChanged": True} mock_client._session = mock_session tool_result = MagicMock() tool_result.content = [MagicMock(text="2")] mock_client.call_tool = AsyncMock(return_value=tool_result) with patch("mcp_test_mcp.connection.Client", return_value=mock_client): # First connection result1 = await connect_to_server("http://server1.example.com/mcp") assert result1["success"] is True # Do some work await call_tool("add", {"a": 1, "b": 1}) # Disconnect await disconnect() # Verify disconnected status = await get_connection_status() assert status["connected"] is False # Reconnect to different server result2 = await connect_to_server("http://server2.example.com/mcp") assert result2["success"] is True assert result2["connection"]["server_url"] == "http://server2.example.com/mcp" # Verify statistics reset assert result2["connection"]["statistics"]["tools_called"] == 0 await disconnect()

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/rdwj/mcp-test-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server