Skip to main content
Glama
vzeman
by vzeman
server.py11.1 kB
"""MCP server for Odoo integration.""" import asyncio import json import os from typing import Any, Dict, List, Optional from dotenv import load_dotenv from mcp.server import Server from mcp.types import TextContent, Tool from pydantic import ValidationError from .odoo_client import OdooClient, OdooConfig # Load environment variables load_dotenv() # Initialize MCP server server = Server("odoo-mcp-server") # Global Odoo client instance odoo_client: Optional[OdooClient] = None def get_odoo_client() -> OdooClient: """Get or create Odoo client instance.""" global odoo_client if odoo_client is None: try: config = OdooConfig( url=os.environ["ODOO_URL"], database=os.environ["ODOO_DB"], username=os.environ["ODOO_USERNAME"], password=os.environ.get("ODOO_PASSWORD"), api_key=os.environ.get("ODOO_API_KEY"), timeout=int(os.environ.get("ODOO_TIMEOUT", "120")), ) odoo_client = OdooClient(config) except (KeyError, ValidationError) as e: raise ValueError(f"Invalid Odoo configuration: {e}") return odoo_client @server.list_tools() async def list_tools() -> List[Tool]: """List available tools.""" return [ Tool( name="search_records", description="Search for Odoo records", inputSchema={ "type": "object", "properties": { "model": { "type": "string", "description": "Odoo model name (e.g., 'res.partner', 'sale.order')", }, "domain": { "type": "array", "description": "Search domain in Odoo format (e.g., [['name', 'ilike', 'john']])", "items": {"type": "array"}, "default": [], }, "fields": { "type": "array", "description": "List of fields to return", "items": {"type": "string"}, "default": None, }, "limit": { "type": "integer", "description": "Maximum number of records to return", "default": None, }, "offset": { "type": "integer", "description": "Number of records to skip", "default": 0, }, "order": { "type": "string", "description": "Sort order (e.g., 'name asc, id desc')", "default": None, }, }, "required": ["model"], }, ), Tool( name="create_record", description="Create a new Odoo record", inputSchema={ "type": "object", "properties": { "model": { "type": "string", "description": "Odoo model name", }, "values": { "type": "object", "description": "Field values for the new record", }, }, "required": ["model", "values"], }, ), Tool( name="update_record", description="Update existing Odoo records", inputSchema={ "type": "object", "properties": { "model": { "type": "string", "description": "Odoo model name", }, "ids": { "type": "array", "description": "List of record IDs to update", "items": {"type": "integer"}, }, "values": { "type": "object", "description": "Field values to update", }, }, "required": ["model", "ids", "values"], }, ), Tool( name="delete_record", description="Delete Odoo records", inputSchema={ "type": "object", "properties": { "model": { "type": "string", "description": "Odoo model name", }, "ids": { "type": "array", "description": "List of record IDs to delete", "items": {"type": "integer"}, }, }, "required": ["model", "ids"], }, ), Tool( name="get_record", description="Get specific Odoo records by ID", inputSchema={ "type": "object", "properties": { "model": { "type": "string", "description": "Odoo model name", }, "ids": { "type": "array", "description": "List of record IDs to retrieve", "items": {"type": "integer"}, }, "fields": { "type": "array", "description": "List of fields to return", "items": {"type": "string"}, "default": None, }, }, "required": ["model", "ids"], }, ), Tool( name="list_models", description="List all available Odoo models", inputSchema={ "type": "object", "properties": { "transient": { "type": "boolean", "description": "Include transient (wizard) models", "default": False, }, }, }, ), Tool( name="get_model_fields", description="Get field definitions for an Odoo model", inputSchema={ "type": "object", "properties": { "model": { "type": "string", "description": "Odoo model name", }, "fields": { "type": "array", "description": "Specific fields to get info for (optional)", "items": {"type": "string"}, "default": None, }, }, "required": ["model"], }, ), ] @server.call_tool() async def call_tool(name: str, arguments: Dict[str, Any]) -> List[TextContent]: """Handle tool calls.""" try: client = get_odoo_client() if name == "search_records": result = await asyncio.to_thread( client.search_read, model=arguments["model"], domain=arguments.get("domain", []), fields=arguments.get("fields"), offset=arguments.get("offset", 0), limit=arguments.get("limit"), order=arguments.get("order"), ) return [TextContent( type="text", text=json.dumps(result, indent=2, default=str) )] elif name == "create_record": result = await asyncio.to_thread( client.create, model=arguments["model"], values=arguments["values"], ) return [TextContent( type="text", text=f"Created record with ID: {result}" )] elif name == "update_record": success = await asyncio.to_thread( client.write, model=arguments["model"], ids=arguments["ids"], values=arguments["values"], ) return [TextContent( type="text", text=f"Update {'successful' if success else 'failed'} for IDs: {arguments['ids']}" )] elif name == "delete_record": success = await asyncio.to_thread( client.unlink, model=arguments["model"], ids=arguments["ids"], ) return [TextContent( type="text", text=f"Delete {'successful' if success else 'failed'} for IDs: {arguments['ids']}" )] elif name == "get_record": result = await asyncio.to_thread( client.read, model=arguments["model"], ids=arguments["ids"], fields=arguments.get("fields"), ) return [TextContent( type="text", text=json.dumps(result, indent=2, default=str) )] elif name == "list_models": models = await asyncio.to_thread(client.get_model_list) if not arguments.get("transient", False): models = [m for m in models if not m.get("transient", False)] # Format output output = "Available Odoo models:\n" for model in sorted(models, key=lambda x: x["model"]): output += f"- {model['model']}: {model['name']}\n" return [TextContent(type="text", text=output)] elif name == "get_model_fields": fields = await asyncio.to_thread( client.fields_get, model=arguments["model"], fields=arguments.get("fields"), ) return [TextContent( type="text", text=json.dumps(fields, indent=2, default=str) )] else: return [TextContent( type="text", text=f"Unknown tool: {name}" )] except Exception as e: return [TextContent( type="text", text=f"Error: {type(e).__name__}: {str(e)}" )] async def main(): """Run the MCP server.""" from mcp.server.stdio import stdio_server async with stdio_server() as (read_stream, write_stream): await server.run( read_stream, write_stream, server.create_initialization_options() ) if __name__ == "__main__": asyncio.run(main())

Latest Blog Posts

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

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