Skip to main content
Glama
test_mcp_roots.py18.8 kB
#!/usr/bin/env python3 """ Test suite for MCP Roots functionality (MCP 2025-03-26) Tests the roots capability including server root discovery, client root requests, and roots/list functionality per the latest MCP specification. """ import requests import json import sys import os from typing import Dict, Any, Optional, List # Removed src directory import to avoid conflicts with running server class MCPRootsTester: def __init__(self, base_url: str = 'http://127.0.0.1:8000/mcp/'): self.base_url = base_url self.session_id = None self.headers = { "Content-Type": "application/json", "Accept": "application/json, text/event-stream" } def parse_sse_response(self, content: str) -> Optional[Dict[str, Any]]: """Parse Server-Sent Events response format.""" if "data: " in content: lines = content.split('\n') for line in lines: if line.startswith("data: "): try: return json.loads(line[6:]) except json.JSONDecodeError: return None return None def initialize_session(self) -> bool: """Initialize MCP session with roots capabilities per 2025-03-26 spec.""" init_data = { 'jsonrpc': '2.0', 'id': 'init', 'method': 'initialize', 'params': { 'protocolVersion': '2025-03-26', 'capabilities': { 'roots': { 'listChanged': True # Client supports roots/list_changed notifications }, 'sampling': {}, 'resources': {} }, 'clientInfo': {'name': 'roots-test', 'version': '1.0'} } } try: response = requests.post(self.base_url, json=init_data, headers=self.headers, timeout=10) if response.status_code == 200: result = self.parse_sse_response(response.text) if result and 'result' in result: server_capabilities = result['result'].get('capabilities', {}) server_roots = server_capabilities.get('roots', {}) self.session_id = response.headers.get('mcp-session-id') self.headers['mcp-session-id'] = self.session_id print(f"✅ Session initialized: {self.session_id}") print(f" Server roots capability: {bool(server_roots)}") if server_roots: print(f" Server supports listChanged: {server_roots.get('listChanged', False)}") # Send initialized notification notif_data = {'jsonrpc': '2.0', 'method': 'notifications/initialized'} requests.post(self.base_url, headers=self.headers, json=notif_data) return True else: print(f"❌ Could not parse initialization response") return False else: print(f"❌ Failed to initialize: {response.status_code}") return False except Exception as e: print(f"❌ Initialization error: {e}") return False def test_roots_list(self) -> bool: """Test the roots/list method per MCP 2025-03-26.""" print(f"\n🌳 Testing roots/list method...") roots_data = { 'jsonrpc': '2.0', 'id': 'roots_list', 'method': 'roots/list', 'params': { '_meta': { 'protocolVersion': '2025-03-26' } } } try: response = requests.post(self.base_url, headers=self.headers, json=roots_data, timeout=10) print(f" Status: {response.status_code}") if response.status_code == 200: result = self.parse_sse_response(response.text) if result and 'result' in result: roots = result['result'].get('roots', []) print(f" ✅ roots/list successful - {len(roots)} roots found") for i, root in enumerate(roots[:5]): # Show first 5 uri = root.get('uri', 'Unknown') name = root.get('name', 'Unknown') print(f" {i+1}. {uri} - {name}") return True else: print(f" ❌ Could not parse roots/list response") return False else: print(f" ❌ roots/list failed: {response.status_code} - {response.text}") return False except Exception as e: print(f" ❌ roots/list error: {e}") return False def test_server_roots_tool(self) -> bool: """Test the get_server_roots tool.""" print(f"\n🔧 Testing get_server_roots tool...") tool_data = { 'jsonrpc': '2.0', 'id': 'server_roots_tool', 'method': 'tools/call', 'params': { 'name': 'get_server_roots', 'arguments': {} } } try: response = requests.post(self.base_url, headers=self.headers, json=tool_data, timeout=10) print(f" Status: {response.status_code}") if response.status_code == 200: result = self.parse_sse_response(response.text) if result and 'result' in result: content = result['result'].get('content', []) if content and 'text' in content[0]: try: roots_data = json.loads(content[0]['text']) if isinstance(roots_data, list): print(f" ✅ get_server_roots successful - {len(roots_data)} roots") for i, root in enumerate(roots_data[:3]): # Show first 3 uri = root.get('uri', 'Unknown') name = root.get('name', 'Unknown') description = root.get('description', 'No description') print(f" {i+1}. {uri}") print(f" Name: {name}") print(f" Description: {description[:60]}...") return True else: print(f" ❌ Unexpected roots data format") return False except json.JSONDecodeError: print(f" ❌ Could not parse roots tool response") return False else: print(f" ❌ No content in tool response") return False else: print(f" ❌ Could not parse tool response") return False else: print(f" ❌ get_server_roots tool failed: {response.status_code}") return False except Exception as e: print(f" ❌ get_server_roots tool error: {e}") return False def test_client_roots_request_tool(self) -> bool: """Test the request_client_roots tool (MCP 2025-03-26 feature).""" print(f"\n📋 Testing request_client_roots tool...") tool_data = { 'jsonrpc': '2.0', 'id': 'client_roots_tool', 'method': 'tools/call', 'params': { 'name': 'request_client_roots', 'arguments': {} } } try: response = requests.post(self.base_url, headers=self.headers, json=tool_data, timeout=10) print(f" Status: {response.status_code}") if response.status_code == 200: result = self.parse_sse_response(response.text) if result and 'result' in result: content = result['result'].get('content', []) if content and 'text' in content[0]: try: response_data = json.loads(content[0]['text']) # Check for roots request structure if 'roots_request' in response_data: roots_req = response_data['roots_request'] method = roots_req.get('method') params = roots_req.get('params', {}) meta = params.get('_meta', {}) print(f" ✅ request_client_roots successful") print(f" Method: {method}") print(f" Protocol version: {meta.get('protocolVersion', 'Unknown')}") print(f" Request reason: {meta.get('requestReason', 'None')}") print(f" Status: {response_data.get('status', 'Unknown')}") # Verify MCP 2025-03-26 compliance if method == 'roots/list' and meta.get('protocolVersion') == '2025-03-26': print(f" ✓ MCP 2025-03-26 compliant roots request") return True else: print(f" ⚠️ Roots request format may not be fully compliant") return True # Still consider it a pass for basic functionality else: print(f" ❌ No roots_request in response") return False except json.JSONDecodeError: print(f" ❌ Could not parse client roots tool response") return False else: print(f" ❌ No content in tool response") return False else: print(f" ❌ Could not parse tool response") return False else: print(f" ❌ request_client_roots tool failed: {response.status_code}") return False except Exception as e: print(f" ❌ request_client_roots tool error: {e}") return False def test_roots_resource(self) -> bool: """Test the roots:// resource endpoint.""" print(f"\n📁 Testing roots:// resource...") resource_data = { 'jsonrpc': '2.0', 'id': 'roots_resource', 'method': 'resources/read', 'params': { 'uri': 'roots://' } } try: response = requests.post(self.base_url, headers=self.headers, json=resource_data, timeout=10) print(f" Status: {response.status_code}") if response.status_code == 200: result = self.parse_sse_response(response.text) if result and 'result' in result: contents = result['result'].get('contents', []) if contents and 'text' in contents[0]: try: roots_info = json.loads(contents[0]['text']) print(f" ✅ roots:// resource successful") print(f" Description: {roots_info.get('description', 'None')}") if 'roots' in roots_info: available_roots = roots_info['roots'] print(f" Available root categories: {len(available_roots)}") for root in available_roots[:5]: # Show first 5 print(f" • {root}") if 'usage' in roots_info: print(f" Usage info: {roots_info['usage']}") return True except json.JSONDecodeError: print(f" ❌ Could not parse roots resource response") return False else: print(f" ❌ No content in resource response") return False else: print(f" ❌ Could not parse resource response") return False else: print(f" ❌ roots:// resource failed: {response.status_code}") return False except Exception as e: print(f" ❌ roots:// resource error: {e}") return False def test_protocol_compliance(self) -> bool: """Test MCP 2025-03-26 protocol compliance for roots.""" print(f"\n🔍 Testing MCP 2025-03-26 protocol compliance...") # Test that we can send a proper roots request with _meta roots_request = { 'jsonrpc': '2.0', 'id': 'compliance_test', 'method': 'roots/list', 'params': { '_meta': { 'protocolVersion': '2025-03-26', 'clientCapabilities': { 'roots': { 'listChanged': True } } } } } try: response = requests.post(self.base_url, headers=self.headers, json=roots_request, timeout=10) print(f" Status: {response.status_code}") if response.status_code == 200: result = self.parse_sse_response(response.text) if result and 'result' in result: # Check if the response includes protocol version info meta = result.get('meta', {}) protocol_version = meta.get('protocolVersion', 'Unknown') print(f" ✅ Protocol compliance test successful") print(f" Response protocol version: {protocol_version}") roots = result['result'].get('roots', []) print(f" Roots returned: {len(roots)}") # Check if roots have proper structure per spec if roots: first_root = roots[0] required_fields = ['uri', 'name'] has_required = all(field in first_root for field in required_fields) print(f" ✓ Roots have required fields: {has_required}") # Check for optional but recommended fields optional_fields = ['description'] has_optional = any(field in first_root for field in optional_fields) print(f" ✓ Roots have optional fields: {has_optional}") return True else: print(f" ❌ Could not parse compliance test response") return False else: print(f" ❌ Protocol compliance test failed: {response.status_code}") return False except Exception as e: print(f" ❌ Protocol compliance test error: {e}") return False def test_all_roots_functionality(): """Test all MCP roots functionality per 2025-03-26 specification.""" print("🌳 MCP Roots Test Suite (MCP 2025-03-26)") print("=" * 50) tester = MCPRootsTester() if not tester.initialize_session(): return False # Test results tracking results = {} # Test 1: roots/list method results['roots_list'] = tester.test_roots_list() # Test 2: get_server_roots tool results['server_roots_tool'] = tester.test_server_roots_tool() # Test 3: request_client_roots tool results['client_roots_request_tool'] = tester.test_client_roots_request_tool() # Test 4: roots:// resource results['roots_resource'] = tester.test_roots_resource() # Test 5: MCP 2025-03-26 protocol compliance results['protocol_compliance'] = tester.test_protocol_compliance() # Print summary print("\n" + "=" * 50) print("📊 ROOTS TEST SUMMARY") print("=" * 50) passed = sum(1 for result in results.values() if result) total = len(results) test_descriptions = { 'roots_list': 'roots/list Method', 'server_roots_tool': 'get_server_roots Tool', 'client_roots_request_tool': 'request_client_roots Tool', 'roots_resource': 'roots:// Resource', 'protocol_compliance': 'MCP 2025-03-26 Compliance' } for test_key, result in results.items(): status = "✅ PASSED" if result else "❌ FAILED" description = test_descriptions.get(test_key, test_key) print(f"{description:30} {status}") print(f"\nOverall Roots Tests: {passed}/{total} passed") # Check critical functionality critical_tests = ['roots_list', 'server_roots_tool', 'protocol_compliance'] critical_passed = sum(1 for test in critical_tests if results.get(test, False)) critical_total = len(critical_tests) print(f"Critical Tests: {critical_passed}/{critical_total} passed") if critical_passed == critical_total: print("✅ All critical roots functionality is working") else: print("⚠️ Some critical roots functionality may not be working properly") return passed == total if __name__ == "__main__": success = test_all_roots_functionality() sys.exit(0 if success else 1)

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/hemanth/paws-on-mcp'

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