Skip to main content
Glama

MCP Server for Odoo

mcp_test_client.py8.99 kB
"""MCP test client for protocol validation.""" import asyncio import logging import os import subprocess import sys import time from contextlib import asynccontextmanager from typing import Any, AsyncIterator, Dict, List, Optional from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client from mcp.types import ( CallToolResult, Resource, TextContent, Tool, ) logger = logging.getLogger(__name__) class MCPTestClient: """Test client for validating MCP server functionality.""" def __init__(self, server_command: Optional[List[str]] = None): """Initialize MCP test client. Args: server_command: Command to start the server. Defaults to ["uvx", "mcp-server-odoo"] """ # Use python module directly since package isn't published self.server_command = server_command or [sys.executable, "-m", "mcp_server_odoo"] self.session: Optional[ClientSession] = None self._server_process: Optional[subprocess.Popen] = None @asynccontextmanager async def connect(self) -> AsyncIterator["MCPTestClient"]: """Connect to MCP server using stdio transport.""" try: # Create server parameters server_params = StdioServerParameters( command=self.server_command[0], args=self.server_command[1:] if len(self.server_command) > 1 else [], env=None, # Will inherit current environment ) # Connect to server async with stdio_client(server_params) as (read, write): async with ClientSession(read, write) as session: self.session = session # Initialize the connection await session.initialize() logger.info("Connected to MCP server") yield self except Exception as e: logger.error(f"Failed to connect to MCP server: {e}") raise finally: self.session = None async def list_resources(self) -> List[Resource]: """List available resources from the server.""" if not self.session: raise RuntimeError("Not connected to server") result = await self.session.list_resources() return result.resources async def read_resource(self, uri: str) -> str: """Read a resource by URI. Args: uri: Resource URI (e.g., "odoo://res.partner/record/1") Returns: Resource content as text """ if not self.session: raise RuntimeError("Not connected to server") result = await self.session.read_resource(uri) # Extract text content if result.contents: for content in result.contents: if isinstance(content, TextContent): return content.text return "" async def list_tools(self) -> List[Tool]: """List available tools from the server.""" if not self.session: raise RuntimeError("Not connected to server") result = await self.session.list_tools() return result.tools async def call_tool(self, name: str, arguments: Dict[str, Any]) -> CallToolResult: """Call a tool with arguments. Args: name: Tool name arguments: Tool arguments Returns: Tool execution result """ if not self.session: raise RuntimeError("Not connected to server") result = await self.session.call_tool(name, arguments) return result async def get_server_info(self) -> Dict[str, Any]: """Get server information from initialization.""" if not self.session: raise RuntimeError("Not connected to server") # Access server info from session return { "name": getattr(self.session, "_server_name", "Unknown"), "version": getattr(self.session, "_server_version", "Unknown"), "capabilities": getattr(self.session, "_server_capabilities", {}), } @staticmethod def start_test_server(env: Optional[Dict[str, str]] = None) -> subprocess.Popen: """Start a test server instance. Args: env: Additional environment variables Returns: Server process """ # Prepare environment test_env = dict(os.environ) if env: test_env.update(env) # Start server process = subprocess.Popen( ["uvx", "mcp-server-odoo"], env=test_env, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, ) # Give server time to start time.sleep(2) # Check if process is still running if process.poll() is not None: stdout, stderr = process.communicate() raise RuntimeError(f"Server failed to start:\nstdout: {stdout}\nstderr: {stderr}") return process @staticmethod def stop_test_server(process: subprocess.Popen) -> None: """Stop a test server instance.""" if process and process.poll() is None: process.terminate() try: process.wait(timeout=5) except subprocess.TimeoutExpired: process.kill() process.wait() async def validate_mcp_response(response: Any, expected_type: type) -> bool: """Validate MCP response format. Args: response: Response to validate expected_type: Expected response type Returns: True if valid """ if not isinstance(response, expected_type): logger.error(f"Invalid response type: expected {expected_type}, got {type(response)}") return False # Additional validation based on type if hasattr(response, "contents"): # Validate resource contents for content in response.contents: if not isinstance(content, (TextContent,)): logger.error(f"Invalid content type: {type(content)}") return False return True async def check_server_capabilities(client: MCPTestClient) -> Dict[str, bool]: """Check server capabilities. Args: client: Connected MCP client Returns: Dictionary of capability test results """ results = {} # Test resource listing try: resources = await client.list_resources() results["list_resources"] = len(resources) > 0 logger.info(f"Found {len(resources)} resources") except Exception as e: logger.error(f"Failed to list resources: {e}") results["list_resources"] = False # Test tool listing try: tools = await client.list_tools() results["list_tools"] = len(tools) > 0 logger.info(f"Found {len(tools)} tools") except Exception as e: logger.error(f"Failed to list tools: {e}") results["list_tools"] = False # Test server info try: info = await client.get_server_info() results["server_info"] = bool(info.get("name")) logger.info(f"Server: {info.get('name')} v{info.get('version')}") except Exception as e: logger.error(f"Failed to get server info: {e}") results["server_info"] = False return results # For direct testing if __name__ == "__main__": import os logging.basicConfig(level=logging.INFO) async def main(): # Check that required environment variables are set required_vars = ["ODOO_URL", "ODOO_API_KEY"] missing_vars = [var for var in required_vars if not os.getenv(var)] if missing_vars: print("ERROR: Missing required environment variables:") for var in missing_vars: print(f" - {var}") print("\nPlease configure these in your .env file") print("Copy .env.example to .env and update with your values") return # Connect to server client = MCPTestClient() async with client.connect() as connected_client: # Test capabilities results = await check_server_capabilities(connected_client) print("\nCapability Test Results:") for capability, passed in results.items(): status = "✓" if passed else "✗" print(f" {status} {capability}") # Try to list resources if results.get("list_resources"): resources = await connected_client.list_resources() print(f"\nAvailable resources: {len(resources)}") for resource in resources[:5]: # Show first 5 print(f" - {resource.uri}: {resource.name}") if len(resources) > 5: print(f" ... and {len(resources) - 5} more") asyncio.run(main())

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/AlejandroLaraPolanco/mcp-odoo'

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