mirror of
https://github.com/apache/superset.git
synced 2026-05-10 18:35:40 +00:00
384 lines
20 KiB
Python
Executable File
384 lines
20 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Simple MCP Service Test Runner
|
|
|
|
This script runs the MCP service integration tests without requiring pytest
|
|
or the full Superset test infrastructure.
|
|
|
|
Usage:
|
|
python run_mcp_tests.py
|
|
|
|
Prerequisites:
|
|
- MCP service must be running on localhost:5008
|
|
- FastMCP must be installed: pip install fastmcp
|
|
"""
|
|
|
|
import logging
|
|
import sys
|
|
|
|
# Configure logging
|
|
logging.basicConfig(
|
|
level=logging.INFO,
|
|
format='%(asctime)s - %(levelname)s - %(message)s'
|
|
)
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def test_mcp_service_connection():
|
|
"""Test connection to MCP service"""
|
|
try:
|
|
from fastmcp import Client
|
|
logger.info("FastMCP imported successfully")
|
|
except ImportError as e:
|
|
logger.error(f"Failed to import FastMCP: {e}")
|
|
logger.error("Please install FastMCP: pip install fastmcp")
|
|
return False
|
|
|
|
try:
|
|
logger.info("Creating MCP client...")
|
|
# Use the correct Client class for HTTP connection
|
|
client = Client("http://localhost:5008/mcp")
|
|
logger.info("MCP client created successfully")
|
|
return client
|
|
except Exception as e:
|
|
logger.error(f"Failed to create MCP client: {e}")
|
|
logger.error("Make sure the MCP service is running on localhost:5008")
|
|
return None
|
|
|
|
|
|
async def test_mcp_tools(client):
|
|
"""Test all available MCP tools"""
|
|
logger.info("Testing MCP tools...")
|
|
|
|
try:
|
|
# Use the client within the async context manager
|
|
async with client:
|
|
# Test ping to verify connection
|
|
await client.ping()
|
|
logger.info("✅ Ping successful - MCP service is reachable")
|
|
|
|
# List available tools
|
|
tools = await client.list_tools()
|
|
logger.info(f"✅ Found {len(tools)} available tools:")
|
|
for tool in tools:
|
|
logger.info(f" - {tool.name}")
|
|
|
|
# Test get_dashboard_info tool
|
|
logger.info("Testing get_dashboard_info tool...")
|
|
try:
|
|
# First, get a list of dashboards to find a valid dashboard ID
|
|
dashboards_result = await client.call_tool("list_dashboards", {"page_size": 1})
|
|
logger.info(f"list_dashboards output (repr): {repr(dashboards_result.data)}")
|
|
if hasattr(dashboards_result.data, "model_dump"):
|
|
logger.info(f"list_dashboards output (dict): {dashboards_result.data.model_dump()}")
|
|
dashboards_data = dashboards_result.data
|
|
if hasattr(dashboards_data, "model_dump"):
|
|
dashboards_dict = dashboards_data.model_dump()
|
|
dashboards_list = dashboards_dict.get("dashboards", [])
|
|
elif hasattr(dashboards_data, "dashboards"):
|
|
dashboards_list = dashboards_data.dashboards
|
|
elif isinstance(dashboards_data, dict):
|
|
dashboards_list = dashboards_data.get("dashboards", [])
|
|
else:
|
|
dashboards_list = []
|
|
if dashboards_list:
|
|
dashboard = dashboards_list[0]
|
|
if hasattr(dashboard, "model_dump"):
|
|
dashboard_dict = dashboard.model_dump()
|
|
dashboard_id = dashboard_dict.get("id")
|
|
elif isinstance(dashboard, dict):
|
|
dashboard_id = dashboard.get("id")
|
|
else:
|
|
dashboard_id = getattr(dashboard, "id", None)
|
|
if not dashboard_id:
|
|
logger.error("❌ Dashboard missing 'id' field")
|
|
return False
|
|
logger.info(f"Testing get_dashboard_info with dashboard ID: {dashboard_id}")
|
|
result = await client.call_tool("get_dashboard_info", {"dashboard_id": dashboard_id})
|
|
logger.info(f"get_dashboard_info output (repr): {repr(result.data)}")
|
|
if hasattr(result.data, "model_dump"):
|
|
logger.info(f"get_dashboard_info output (dict): {result.data.model_dump()}")
|
|
logger.info("✅ get_dashboard_info tool call successful")
|
|
else:
|
|
logger.warning("No dashboards found to test get_dashboard_info with a real ID. Skipping this test.")
|
|
except Exception as e:
|
|
logger.error(f"❌ get_dashboard_info failed: {e}")
|
|
return False
|
|
|
|
# Test get_dashboard_info with invalid parameters
|
|
logger.info("Testing get_dashboard_info with invalid parameters...")
|
|
try:
|
|
result = await client.call_tool("get_dashboard_info", {"invalid_param": "value"})
|
|
logger.info(f"get_dashboard_info output (repr, invalid param): {repr(result.data)}")
|
|
if hasattr(result.data, "model_dump"):
|
|
logger.info(f"get_dashboard_info output (dict, invalid param): {result.data.model_dump()}")
|
|
logger.warning("⚠️ get_dashboard_info should have failed with invalid parameters")
|
|
except Exception as e:
|
|
logger.info(f"✅ get_dashboard_info correctly rejected invalid parameters: {e}")
|
|
|
|
# Test list_dashboards tool
|
|
logger.info("Testing list_dashboards tool...")
|
|
try:
|
|
result = await client.call_tool("list_dashboards", {})
|
|
logger.info(f"list_dashboards output (repr): {repr(result.data)}")
|
|
# Always convert to dict if possible
|
|
data_dict = None
|
|
if hasattr(result.data, "model_dump"):
|
|
data_dict = result.data.model_dump()
|
|
logger.info(f"list_dashboards output (dict): {data_dict}")
|
|
elif isinstance(result.data, dict):
|
|
data_dict = result.data
|
|
logger.info(f"list_dashboards output (dict): {data_dict}")
|
|
else:
|
|
logger.warning(f"list_dashboards returned a non-dict, non-Pydantic type: {type(result.data)}. Skipping validation.")
|
|
logger.info("✅ list_dashboards tool call successful (skipped validation)")
|
|
return True
|
|
logger.info("✅ list_dashboards tool call successful")
|
|
# Validate response structure
|
|
if data_dict is not None:
|
|
expected_fields = ["dashboards", "count", "total_count"]
|
|
missing_fields = [field for field in expected_fields if field not in data_dict]
|
|
if missing_fields:
|
|
logger.error(f"❌ list_dashboards missing expected fields: {missing_fields}")
|
|
return False
|
|
if not isinstance(data_dict["dashboards"], list):
|
|
logger.error(f"❌ 'dashboards' should be list, got {type(data_dict['dashboards'])}")
|
|
return False
|
|
if not isinstance(data_dict["count"], int):
|
|
logger.error(f"❌ 'count' should be int, got {type(data_dict['count'])}")
|
|
return False
|
|
if not isinstance(data_dict["total_count"], int):
|
|
logger.error(f"❌ 'total_count' should be int, got {type(data_dict['total_count'])}")
|
|
return False
|
|
logger.info(f"Found {len(data_dict['dashboards'])} dashboards")
|
|
if len(data_dict["dashboards"]) > 0:
|
|
dashboard = data_dict["dashboards"][0]
|
|
# if hasattr(dashboard, "model_dump"):
|
|
# dashboard = dashboard.model_dump()
|
|
if not isinstance(dashboard, dict):
|
|
logger.error(f"❌ Dashboard should be dict, got {type(dashboard)}")
|
|
return False
|
|
required_fields = ["id", "dashboard_title"]
|
|
missing_fields = [field for field in required_fields if field not in dashboard]
|
|
if missing_fields:
|
|
logger.error(f"❌ Dashboard missing required fields: {missing_fields}")
|
|
return False
|
|
logger.info(f"✅ First dashboard validated: {dashboard.get('dashboard_title', 'N/A')}")
|
|
except Exception as e:
|
|
logger.error(f"❌ list_dashboards failed: {e}")
|
|
return False
|
|
|
|
# Test list_dashboards tool
|
|
logger.info("Testing list_dashboards tool...")
|
|
try:
|
|
result = await client.call_tool("list_dashboards", {})
|
|
logger.info(f"list_dashboards output (repr): {repr(result.data)}")
|
|
data_dict = None
|
|
if hasattr(result.data, "model_dump"):
|
|
data_dict = result.data.model_dump()
|
|
logger.info(f"list_dashboards output (dict): {data_dict}")
|
|
elif isinstance(result.data, dict):
|
|
data_dict = result.data
|
|
logger.info(f"list_dashboards output (dict): {data_dict}")
|
|
else:
|
|
logger.warning(f"list_dashboards returned a non-dict, non-Pydantic type: {type(result.data)}. Skipping validation.")
|
|
logger.info("✅ list_dashboards tool call successful (skipped validation)")
|
|
return True
|
|
logger.info("✅ list_dashboards tool call successful")
|
|
if data_dict is not None:
|
|
expected_fields = ["dashboards", "count", "total_count"]
|
|
missing_fields = [field for field in expected_fields if field not in data_dict]
|
|
if missing_fields:
|
|
logger.error(f"❌ list_dashboards missing expected fields: {missing_fields}")
|
|
return False
|
|
if not isinstance(data_dict["dashboards"], list):
|
|
logger.error(f"❌ 'dashboards' should be list, got {type(data_dict['dashboards'])}")
|
|
return False
|
|
if not isinstance(data_dict["count"], int):
|
|
logger.error(f"❌ 'count' should be int, got {type(data_dict['count'])}")
|
|
return False
|
|
if not isinstance(data_dict["total_count"], int):
|
|
logger.error(f"❌ 'total_count' should be int, got {type(data_dict['total_count'])}")
|
|
return False
|
|
logger.info(f"✅ list_dashboards response validated: {data_dict['count']} dashboards")
|
|
except Exception as e:
|
|
logger.error(f"❌ list_dashboards failed: {e}")
|
|
return False
|
|
|
|
# Test get_dashboard_available_filters tool
|
|
logger.info("Testing get_dashboard_available_filters tool...")
|
|
try:
|
|
result = await client.call_tool("get_dashboard_available_filters", {})
|
|
logger.info(f"get_dashboard_available_filters output (repr): {repr(result.data)}")
|
|
if hasattr(result.data, "model_dump"):
|
|
logger.info(f"get_dashboard_available_filters output (dict): {result.data.model_dump()}")
|
|
logger.info("✅ get_dashboard_available_filters tool call successful")
|
|
|
|
# Validate response structure
|
|
if not isinstance(result.data, dict):
|
|
pass
|
|
# Check for expected fields
|
|
expected_fields = ["filters", "operators", "columns"]
|
|
missing_fields = [field for field in expected_fields if field not in result.data]
|
|
if missing_fields:
|
|
logger.error(f"❌ get_dashboard_available_filters missing expected fields: {missing_fields}")
|
|
return False
|
|
|
|
# Validate field types
|
|
if not isinstance(result.data["filters"], dict):
|
|
logger.error(f"❌ 'filters' should be dict, got {type(result.data['filters'])}")
|
|
return False
|
|
|
|
if not isinstance(result.data["operators"], list):
|
|
logger.error(f"❌ 'operators' should be list, got {type(result.data['operators'])}")
|
|
return False
|
|
|
|
if not isinstance(result.data["columns"], list):
|
|
logger.error(f"❌ 'columns' should be list, got {type(result.data['columns'])}")
|
|
return False
|
|
|
|
logger.info(f"✅ get_dashboard_available_filters response validated: {len(result.data['filters'])} filters, {len(result.data['operators'])} operators")
|
|
|
|
except Exception as e:
|
|
logger.error(f"❌ get_dashboard_available_filters failed: {e}")
|
|
return False
|
|
|
|
# Test get_superset_instance_info tool
|
|
logger.info("Testing get_superset_instance_info tool...")
|
|
try:
|
|
result = await client.call_tool("get_superset_instance_info", {})
|
|
logger.info(f"get_superset_instance_info output (repr): {repr(result.data)}")
|
|
if hasattr(result.data, "model_dump"):
|
|
logger.info(f"get_superset_instance_info output (dict): {result.data.model_dump()}")
|
|
logger.info("✅ get_superset_instance_info tool call successful")
|
|
|
|
# Validate structure
|
|
missing_fields = [f for f in ["instance_summary", "recent_activity"] if f not in result.data]
|
|
if missing_fields:
|
|
logger.error(f"❌ get_superset_instance_info missing expected fields: {missing_fields}")
|
|
else:
|
|
logger.info(f"✅ get_superset_instance_info response validated: {result.data['instance_summary']}")
|
|
except Exception as e:
|
|
logger.error(f"❌ get_superset_instance_info failed: {e}")
|
|
return False
|
|
|
|
# Test list_datasets_simple tool
|
|
logger.info("Testing list_datasets_simple tool...")
|
|
try:
|
|
result = await client.call_tool("list_datasets_simple", {})
|
|
logger.info(f"list_datasets_simple output (repr): {repr(result.data)}")
|
|
data_dict = None
|
|
if hasattr(result.data, "model_dump"):
|
|
data_dict = result.data.model_dump()
|
|
logger.info(f"list_datasets_simple output (dict): {data_dict}")
|
|
elif isinstance(result.data, dict):
|
|
data_dict = result.data
|
|
logger.info(f"list_datasets_simple output (dict): {data_dict}")
|
|
else:
|
|
logger.warning(f"list_datasets_simple returned a non-dict, non-Pydantic type: {type(result.data)}. Skipping validation.")
|
|
logger.info("✅ list_datasets_simple tool call successful (skipped validation)")
|
|
return True
|
|
logger.info("✅ list_datasets_simple tool call successful")
|
|
if data_dict is not None:
|
|
expected_fields = ["datasets", "count", "total_count"]
|
|
missing_fields = [field for field in expected_fields if field not in data_dict]
|
|
if missing_fields:
|
|
logger.error(f"❌ list_datasets_simple missing expected fields: {missing_fields}")
|
|
return False
|
|
if not isinstance(data_dict["datasets"], list):
|
|
logger.error(f"❌ 'datasets' should be list, got {type(data_dict['datasets'])}")
|
|
return False
|
|
if not isinstance(data_dict["count"], int):
|
|
logger.error(f"❌ 'count' should be int, got {type(data_dict['count'])}")
|
|
return False
|
|
if not isinstance(data_dict["total_count"], int):
|
|
logger.error(f"❌ 'total_count' should be int, got {type(data_dict['total_count'])}")
|
|
return False
|
|
logger.info(f"Found {len(data_dict['datasets'])} datasets")
|
|
if len(data_dict["datasets"]) > 0:
|
|
dataset = data_dict["datasets"][0]
|
|
if hasattr(dataset, "model_dump"):
|
|
dataset = dataset.model_dump()
|
|
if not isinstance(dataset, dict):
|
|
logger.error(f"❌ DatasetInfo should be dict, got {type(dataset)}")
|
|
return False
|
|
required_fields = ["id", "table_name"]
|
|
missing_fields = [field for field in required_fields if field not in dataset]
|
|
if missing_fields:
|
|
logger.error(f"❌ DatasetInfo missing required fields: {missing_fields}")
|
|
return False
|
|
logger.info(f"✅ First dataset validated: {dataset.get('table_name', 'N/A')}")
|
|
except Exception as e:
|
|
logger.error(f"❌ list_datasets_simple failed: {e}")
|
|
return False
|
|
|
|
# Test list_datasets tool
|
|
logger.info("Testing list_datasets tool...")
|
|
try:
|
|
result = await client.call_tool("list_datasets", {})
|
|
logger.info(f"list_datasets output (repr): {repr(result.data)}")
|
|
data_dict = None
|
|
if hasattr(result.data, "model_dump"):
|
|
data_dict = result.data.model_dump()
|
|
logger.info(f"list_datasets output (dict): {data_dict}")
|
|
elif isinstance(result.data, dict):
|
|
data_dict = result.data
|
|
logger.info(f"list_datasets output (dict): {data_dict}")
|
|
else:
|
|
logger.warning(f"list_datasets returned a non-dict, non-Pydantic type: {type(result.data)}. Skipping validation.")
|
|
logger.info("✅ list_datasets tool call successful (skipped validation)")
|
|
return True
|
|
logger.info("✅ list_datasets tool call successful")
|
|
if data_dict is not None:
|
|
expected_fields = ["datasets", "count", "total_count"]
|
|
missing_fields = [field for field in expected_fields if field not in data_dict]
|
|
if missing_fields:
|
|
logger.error(f"❌ list_datasets missing expected fields: {missing_fields}")
|
|
return False
|
|
if not isinstance(data_dict["datasets"], list):
|
|
logger.error(f"❌ 'datasets' should be list, got {type(data_dict['datasets'])}")
|
|
return False
|
|
if not isinstance(data_dict["count"], int):
|
|
logger.error(f"❌ 'count' should be int, got {type(data_dict['count'])}")
|
|
return False
|
|
if not isinstance(data_dict["total_count"], int):
|
|
logger.error(f"❌ 'total_count' should be int, got {type(data_dict['total_count'])}")
|
|
return False
|
|
logger.info(f"✅ list_datasets response validated: {data_dict['count']} datasets")
|
|
except Exception as e:
|
|
logger.error(f"❌ list_datasets failed: {e}")
|
|
return False
|
|
|
|
return True
|
|
|
|
except Exception as e:
|
|
logger.error(f"❌ Failed to test MCP tools: {e}")
|
|
return False
|
|
|
|
|
|
async def main():
|
|
"""Main test function"""
|
|
logger.info("Starting MCP Service Integration Tests")
|
|
logger.info("=" * 60)
|
|
|
|
# Test connection
|
|
client = test_mcp_service_connection()
|
|
if not client:
|
|
logger.error("❌ Failed to connect to MCP service")
|
|
sys.exit(1)
|
|
|
|
# Test tools
|
|
success = await test_mcp_tools(client)
|
|
|
|
if success:
|
|
logger.info("✅ All MCP service tests completed successfully!")
|
|
sys.exit(0)
|
|
else:
|
|
logger.error("❌ Some MCP service tests failed")
|
|
sys.exit(1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import asyncio
|
|
asyncio.run(main())
|