mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-01-09 08:08:32 +00:00
376 lines
11 KiB
Python
376 lines
11 KiB
Python
"""Shared fixtures for MCP testing."""
|
|
|
|
import pytest
|
|
from unittest.mock import Mock
|
|
|
|
# Import from the source directory
|
|
import sys
|
|
import os
|
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../../src'))
|
|
|
|
from crewai.agent import Agent
|
|
from crewai.tools.mcp_tool_wrapper import MCPToolWrapper
|
|
from tests.mocks.mcp_server_mock import MockMCPServerFactory
|
|
|
|
|
|
@pytest.fixture
|
|
def sample_mcp_agent():
|
|
"""Create a sample agent with MCP configuration for testing."""
|
|
return Agent(
|
|
role="Test MCP Agent",
|
|
goal="Test MCP functionality",
|
|
backstory="Agent designed for MCP testing",
|
|
mcps=["https://api.test.com/mcp"]
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def multi_mcp_agent():
|
|
"""Create agent with multiple MCP configurations."""
|
|
return Agent(
|
|
role="Multi-MCP Agent",
|
|
goal="Test multiple MCP server integration",
|
|
backstory="Agent with access to multiple MCP servers",
|
|
mcps=[
|
|
"https://search.server.com/mcp",
|
|
"https://analysis.server.com/mcp#specific_tool",
|
|
"crewai-amp:research-tools",
|
|
"crewai-amp:financial-data#stock_prices"
|
|
]
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def mcp_agent_no_tools():
|
|
"""Create agent without MCP configuration."""
|
|
return Agent(
|
|
role="No MCP Agent",
|
|
goal="Test without MCP tools",
|
|
backstory="Standard agent without MCP access"
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def sample_mcp_tool_wrapper():
|
|
"""Create a sample MCPToolWrapper for testing."""
|
|
return MCPToolWrapper(
|
|
mcp_server_params={"url": "https://test.server.com/mcp"},
|
|
tool_name="test_tool",
|
|
tool_schema={
|
|
"description": "Test tool for MCP integration",
|
|
"args_schema": None
|
|
},
|
|
server_name="test_server_com_mcp"
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_mcp_tool_schemas():
|
|
"""Provide mock MCP tool schemas for testing."""
|
|
return {
|
|
"search_web": {
|
|
"description": "Search the web for information",
|
|
"args_schema": None
|
|
},
|
|
"analyze_data": {
|
|
"description": "Analyze provided data and generate insights",
|
|
"args_schema": None
|
|
},
|
|
"get_weather": {
|
|
"description": "Get weather information for a location",
|
|
"args_schema": None
|
|
}
|
|
}
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_exa_like_tools():
|
|
"""Provide mock tools similar to Exa MCP server."""
|
|
tools = []
|
|
|
|
# Web search tool
|
|
web_search = Mock(spec=MCPToolWrapper)
|
|
web_search.name = "mcp_exa_ai_mcp_web_search_exa"
|
|
web_search.description = "Search the web using Exa AI"
|
|
web_search.original_tool_name = "web_search_exa"
|
|
web_search.server_name = "mcp_exa_ai_mcp"
|
|
tools.append(web_search)
|
|
|
|
# Code context tool
|
|
code_context = Mock(spec=MCPToolWrapper)
|
|
code_context.name = "mcp_exa_ai_mcp_get_code_context_exa"
|
|
code_context.description = "Get code context using Exa"
|
|
code_context.original_tool_name = "get_code_context_exa"
|
|
code_context.server_name = "mcp_exa_ai_mcp"
|
|
tools.append(code_context)
|
|
|
|
return tools
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_weather_like_tools():
|
|
"""Provide mock tools similar to weather MCP server."""
|
|
tools = []
|
|
|
|
weather_tools = [
|
|
("get_current_weather", "Get current weather conditions"),
|
|
("get_forecast", "Get weather forecast for next 5 days"),
|
|
("get_alerts", "Get active weather alerts")
|
|
]
|
|
|
|
for tool_name, description in weather_tools:
|
|
tool = Mock(spec=MCPToolWrapper)
|
|
tool.name = f"weather_server_com_mcp_{tool_name}"
|
|
tool.description = description
|
|
tool.original_tool_name = tool_name
|
|
tool.server_name = "weather_server_com_mcp"
|
|
tools.append(tool)
|
|
|
|
return tools
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_amp_mcp_responses():
|
|
"""Provide mock responses for CrewAI AMP MCP API calls."""
|
|
return {
|
|
"research-tools": [
|
|
{"url": "https://amp.crewai.com/mcp/research/v1"},
|
|
{"url": "https://amp.crewai.com/mcp/research/v2"}
|
|
],
|
|
"financial-data": [
|
|
{"url": "https://amp.crewai.com/mcp/financial/main"}
|
|
],
|
|
"weather-service": [
|
|
{"url": "https://amp.crewai.com/mcp/weather/api"}
|
|
]
|
|
}
|
|
|
|
|
|
@pytest.fixture
|
|
def performance_test_mcps():
|
|
"""Provide MCP configurations for performance testing."""
|
|
return [
|
|
"https://fast-server.com/mcp",
|
|
"https://medium-server.com/mcp",
|
|
"https://reliable-server.com/mcp"
|
|
]
|
|
|
|
|
|
@pytest.fixture
|
|
def error_scenario_mcps():
|
|
"""Provide MCP configurations for error scenario testing."""
|
|
return [
|
|
"https://timeout-server.com/mcp",
|
|
"https://auth-fail-server.com/mcp",
|
|
"https://json-error-server.com/mcp",
|
|
"https://not-found-server.com/mcp"
|
|
]
|
|
|
|
|
|
@pytest.fixture
|
|
def mixed_quality_mcps():
|
|
"""Provide mixed quality MCP server configurations for resilience testing."""
|
|
return [
|
|
"https://excellent-server.com/mcp", # Always works
|
|
"https://intermittent-server.com/mcp", # Sometimes works
|
|
"https://slow-but-working-server.com/mcp", # Slow but reliable
|
|
"https://completely-broken-server.com/mcp" # Never works
|
|
]
|
|
|
|
|
|
@pytest.fixture
|
|
def server_name_test_cases():
|
|
"""Provide test cases for server name extraction."""
|
|
return [
|
|
# (input_url, expected_server_name)
|
|
("https://api.example.com/mcp", "api_example_com_mcp"),
|
|
("https://mcp.exa.ai/api/v1", "mcp_exa_ai_api_v1"),
|
|
("https://simple.com", "simple_com"),
|
|
("https://complex-domain.co.uk/deep/path/mcp", "complex-domain_co_uk_deep_path_mcp"),
|
|
("https://localhost:8080/mcp", "localhost:8080_mcp"),
|
|
]
|
|
|
|
|
|
@pytest.fixture
|
|
def mcp_reference_parsing_cases():
|
|
"""Provide test cases for MCP reference parsing."""
|
|
return [
|
|
# (mcp_ref, expected_type, expected_server, expected_tool)
|
|
("https://api.example.com/mcp", "external", "https://api.example.com/mcp", None),
|
|
("https://api.example.com/mcp#search", "external", "https://api.example.com/mcp", "search"),
|
|
("crewai-amp:weather-service", "amp", "weather-service", None),
|
|
("crewai-amp:financial-data#stock_price", "amp", "financial-data", "stock_price"),
|
|
]
|
|
|
|
|
|
@pytest.fixture
|
|
def cache_test_scenarios():
|
|
"""Provide scenarios for cache testing."""
|
|
return {
|
|
"cache_hit": {
|
|
"initial_time": 1000,
|
|
"subsequent_time": 1100, # Within 300s TTL
|
|
"expected_calls": 1
|
|
},
|
|
"cache_miss": {
|
|
"initial_time": 1000,
|
|
"subsequent_time": 1400, # Beyond 300s TTL
|
|
"expected_calls": 2
|
|
},
|
|
"cache_boundary": {
|
|
"initial_time": 1000,
|
|
"subsequent_time": 1300, # Exactly at 300s TTL boundary
|
|
"expected_calls": 2
|
|
}
|
|
}
|
|
|
|
|
|
@pytest.fixture
|
|
def timeout_test_scenarios():
|
|
"""Provide scenarios for timeout testing."""
|
|
return {
|
|
"connection_timeout": {
|
|
"timeout_type": "connection",
|
|
"delay": 15, # Exceeds 10s connection timeout
|
|
"expected_error": "timed out"
|
|
},
|
|
"execution_timeout": {
|
|
"timeout_type": "execution",
|
|
"delay": 35, # Exceeds 30s execution timeout
|
|
"expected_error": "timed out"
|
|
},
|
|
"discovery_timeout": {
|
|
"timeout_type": "discovery",
|
|
"delay": 20, # Exceeds 15s discovery timeout
|
|
"expected_error": "timed out"
|
|
}
|
|
}
|
|
|
|
|
|
@pytest.fixture
|
|
def mcp_error_scenarios():
|
|
"""Provide various MCP error scenarios for testing."""
|
|
return {
|
|
"connection_refused": {
|
|
"error": ConnectionRefusedError("Connection refused"),
|
|
"expected_msg": "network connection failed",
|
|
"retryable": True
|
|
},
|
|
"auth_failed": {
|
|
"error": Exception("Authentication failed"),
|
|
"expected_msg": "authentication failed",
|
|
"retryable": False
|
|
},
|
|
"json_parse_error": {
|
|
"error": ValueError("JSON decode error"),
|
|
"expected_msg": "server response parsing error",
|
|
"retryable": True
|
|
},
|
|
"tool_not_found": {
|
|
"error": Exception("Tool not found"),
|
|
"expected_msg": "not found",
|
|
"retryable": False
|
|
},
|
|
"server_error": {
|
|
"error": Exception("Internal server error"),
|
|
"expected_msg": "mcp execution error",
|
|
"retryable": False
|
|
}
|
|
}
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def clear_mcp_cache():
|
|
"""Automatically clear MCP cache before each test."""
|
|
from crewai.agent import _mcp_schema_cache
|
|
_mcp_schema_cache.clear()
|
|
yield
|
|
_mcp_schema_cache.clear()
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_successful_mcp_execution():
|
|
"""Provide a mock for successful MCP tool execution."""
|
|
def _mock_execution(**kwargs):
|
|
return f"Successful MCP execution with args: {kwargs}"
|
|
return _mock_execution
|
|
|
|
|
|
@pytest.fixture
|
|
def performance_benchmarks():
|
|
"""Provide performance benchmarks for MCP operations."""
|
|
return {
|
|
"agent_creation_max_time": 0.5, # 500ms
|
|
"tool_discovery_max_time": 2.0, # 2 seconds
|
|
"cache_hit_max_time": 0.01, # 10ms
|
|
"tool_execution_max_time": 35.0, # 35 seconds (includes timeout buffer)
|
|
"crew_integration_max_time": 0.1 # 100ms
|
|
}
|
|
|
|
|
|
# Convenience functions for common test setup
|
|
|
|
def setup_successful_mcp_environment():
|
|
"""Set up a complete successful MCP test environment."""
|
|
mock_server = MockMCPServerFactory.create_exa_like_server("https://mock-exa.com/mcp")
|
|
|
|
agent = Agent(
|
|
role="Success Test Agent",
|
|
goal="Test successful MCP operations",
|
|
backstory="Agent for testing successful scenarios",
|
|
mcps=["https://mock-exa.com/mcp"]
|
|
)
|
|
|
|
return agent, mock_server
|
|
|
|
|
|
def setup_error_prone_mcp_environment():
|
|
"""Set up an MCP test environment with various error conditions."""
|
|
agents = {}
|
|
|
|
# Different agents for different error scenarios
|
|
agents["timeout"] = Agent(
|
|
role="Timeout Agent",
|
|
goal="Test timeout scenarios",
|
|
backstory="Agent for timeout testing",
|
|
mcps=["https://slow-server.com/mcp"]
|
|
)
|
|
|
|
agents["auth_fail"] = Agent(
|
|
role="Auth Fail Agent",
|
|
goal="Test auth failures",
|
|
backstory="Agent for auth testing",
|
|
mcps=["https://secure-server.com/mcp"]
|
|
)
|
|
|
|
agents["mixed"] = Agent(
|
|
role="Mixed Results Agent",
|
|
goal="Test mixed success/failure",
|
|
backstory="Agent for mixed scenario testing",
|
|
mcps=[
|
|
"https://working-server.com/mcp",
|
|
"https://failing-server.com/mcp",
|
|
"crewai-amp:working-service",
|
|
"crewai-amp:failing-service"
|
|
]
|
|
)
|
|
|
|
return agents
|
|
|
|
|
|
def create_test_crew_with_mcp_agents(agents, task_descriptions=None):
|
|
"""Create a test crew with MCP-enabled agents."""
|
|
if task_descriptions is None:
|
|
task_descriptions = ["Generic test task" for _ in agents]
|
|
|
|
tasks = []
|
|
for i, agent in enumerate(agents):
|
|
task = Task(
|
|
description=task_descriptions[i] if i < len(task_descriptions) else f"Task for {agent.role}",
|
|
expected_output=f"Output from {agent.role}",
|
|
agent=agent
|
|
)
|
|
tasks.append(task)
|
|
|
|
return Crew(agents=agents, tasks=tasks)
|