Skip to main content
Glama

MCP Server for Odoo

server.py•9.81 kB
"""MCP Server implementation for Odoo. This module provides the FastMCP server that exposes Odoo data and functionality through the Model Context Protocol. """ from typing import Any, Dict, Optional from mcp.server import FastMCP from .access_control import AccessController from .config import OdooConfig, get_config from .error_handling import ( ConfigurationError, ErrorContext, error_handler, ) from .logging_config import get_logger, logging_config, perf_logger from .odoo_connection import OdooConnection, OdooConnectionError from .performance import PerformanceManager from .resources import register_resources from .tools import register_tools # Set up logging logger = get_logger(__name__) # Server version SERVER_VERSION = "0.1.0" class OdooMCPServer: """Main MCP server class for Odoo integration. This class manages the FastMCP server instance and maintains the connection to Odoo. The server lifecycle is managed by establishing connection before starting and cleaning up on exit. """ def __init__(self, config: Optional[OdooConfig] = None): """Initialize the Odoo MCP server. Args: config: Optional OdooConfig instance. If not provided, will load from environment variables. """ # Load configuration self.config = config or get_config() # Set up structured logging logging_config.setup() # Initialize connection and access controller (will be created on startup) self.connection: Optional[OdooConnection] = None self.access_controller: Optional[AccessController] = None self.performance_manager: Optional[PerformanceManager] = None self.resource_handler = None self.tool_handler = None # Create FastMCP instance with server metadata self.app = FastMCP( name="odoo-mcp-server", instructions="MCP server for accessing and managing Odoo ERP data through the Model Context Protocol", ) logger.info(f"Initialized Odoo MCP Server v{SERVER_VERSION}") def _ensure_connection(self): """Ensure connection to Odoo is established. Raises: ConnectionError: If connection fails ConfigurationError: If configuration is invalid """ if not self.connection: try: logger.info("Establishing connection to Odoo...") with perf_logger.track_operation("connection_setup"): # Create performance manager (shared across components) self.performance_manager = PerformanceManager(self.config) # Create connection with performance manager self.connection = OdooConnection( self.config, performance_manager=self.performance_manager ) # Connect and authenticate self.connection.connect() self.connection.authenticate() logger.info(f"Successfully connected to Odoo at {self.config.url}") # Initialize access controller self.access_controller = AccessController(self.config) except Exception as e: context = ErrorContext(operation="connection_setup") # Let specific errors propagate as-is if isinstance(e, (OdooConnectionError, ConfigurationError)): raise # Handle other unexpected errors error_handler.handle_error(e, context=context) def _cleanup_connection(self): """Clean up Odoo connection.""" if self.connection: try: logger.info("Closing Odoo connection...") self.connection.disconnect() except Exception as e: logger.error(f"Error closing connection: {e}") finally: # Always clear connection reference self.connection = None self.access_controller = None self.resource_handler = None self.tool_handler = None def _setup_handlers(self): """Set up MCP handlers for resources, tools, and prompts. This method will be extended in later phases to add: - Resource handlers for Odoo data access - Tool handlers for Odoo operations - Prompt handlers for guided workflows """ # TODO: Tools will be added in Phase 3 # TODO: Prompts will be added in Phase 4 pass def _register_resources(self): """Register resource handlers after connection is established.""" if self.connection and self.access_controller: self.resource_handler = register_resources( self.app, self.connection, self.access_controller, self.config ) logger.info("Registered MCP resources") def _register_tools(self): """Register tool handlers after connection is established.""" if self.connection and self.access_controller: self.tool_handler = register_tools( self.app, self.connection, self.access_controller, self.config ) logger.info("Registered MCP tools") async def run_stdio(self): """Run the server using stdio transport. This is the main entry point for running the server with standard input/output transport (used by uvx). """ try: # Establish connection before starting server with perf_logger.track_operation("server_startup"): self._ensure_connection() # Register resources after connection is established self._register_resources() self._register_tools() logger.info("Starting MCP server with stdio transport...") await self.app.run_stdio_async() except KeyboardInterrupt: logger.info("Server interrupted by user") except (OdooConnectionError, ConfigurationError): # Let these specific errors propagate raise except Exception as e: context = ErrorContext(operation="server_run") error_handler.handle_error(e, context=context) finally: # Always cleanup connection self._cleanup_connection() def run_stdio_sync(self): """Synchronous wrapper for run_stdio. This is provided for compatibility with synchronous code. """ import asyncio asyncio.run(self.run_stdio()) # SSE transport has been deprecated in MCP protocol version 2025-03-26 # Use streamable-http transport instead async def run_http(self, host: str = "localhost", port: int = 8000): """Run the server using streamable HTTP transport. Args: host: Host to bind to port: Port to bind to """ try: # Establish connection before starting server with perf_logger.track_operation("server_startup"): self._ensure_connection() # Register resources after connection is established self._register_resources() self._register_tools() logger.info(f"Starting MCP server with HTTP transport on {host}:{port}...") # Update FastMCP settings for host and port self.app.settings.host = host self.app.settings.port = port # Use the specific streamable HTTP async method await self.app.run_streamable_http_async() except KeyboardInterrupt: logger.info("Server interrupted by user") except (OdooConnectionError, ConfigurationError): # Let these specific errors propagate raise except Exception as e: context = ErrorContext(operation="server_run_http") error_handler.handle_error(e, context=context) finally: # Always cleanup connection self._cleanup_connection() def get_capabilities(self) -> Dict[str, Dict[str, bool]]: """Get server capabilities. Returns: Dict with server capabilities """ return { "capabilities": { "resources": True, # Exposes Odoo data as resources "tools": True, # Provides tools for Odoo operations "prompts": False, # Prompts will be added in later phases } } def get_health_status(self) -> Dict[str, Any]: """Get server health status with error metrics. Returns: Dict with health status and metrics """ is_connected = ( self.connection and self.connection.is_authenticated if hasattr(self.connection, "is_authenticated") else False ) # Get performance stats if available performance_stats = None if self.performance_manager: performance_stats = self.performance_manager.get_stats() return { "status": "healthy" if is_connected else "unhealthy", "version": SERVER_VERSION, "connection": { "connected": is_connected, "url": self.config.url if self.config else None, "database": ( self.connection.database if self.connection and hasattr(self.connection, "database") else None ), }, "error_metrics": error_handler.get_metrics(), "recent_errors": error_handler.get_recent_errors(limit=5), "performance": performance_stats, }

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