Skip to main content
Glama

Odoo MCP Server Advanced

by AlanOgic
CLAUDE.md19.1 kB
# CLAUDE.md - Odoo MCP Server v1.0.0-beta This file provides guidance to Claude Code (claude.ai/code) when working with the Odoo MCP Server Advanced. **Philosophy: Radical Simplicity** - **Two tools**: `execute_method` and `batch_execute` - **Infinite possibilities**: Full Odoo API access - **Smart limits**: Automatic protection against massive data returns - **Power user focused**: Documentation over specialized tools --- ## Quick Start ### Installation **Recommended: uvx (no installation needed)** ```bash # Run directly uvx --from odoo-mcp odoo-mcp # From source directory uvx --from . odoo-mcp ``` **Traditional: pip install** ```bash # From source with dev dependencies pip install -e ".[dev]" # Or from PyPI (when published) pip install odoo-mcp ``` ### Configuration **Create `.env` file:** ```bash ODOO_URL=https://your-odoo-instance.com ODOO_DB=your-database-name ODOO_USERNAME=your-username ODOO_PASSWORD=your-password-or-api-key ``` **Optional environment variables:** ```bash ODOO_TIMEOUT=30 # Connection timeout (default: 30s) ODOO_VERIFY_SSL=true # SSL verification (default: true) HTTP_PROXY=http://proxy # HTTP proxy for Odoo connection ``` ### Running the Server **STDIO (Claude Desktop):** ```bash # With uvx uvx --from . odoo-mcp # With Python module python -m odoo_mcp # Standalone script (enhanced logging) python run_server.py ``` **SSE (Web browsers):** ```bash python run_server_sse.py # Listens on http://0.0.0.0:8009/sse ``` **HTTP (API integrations):** ```bash python run_server_http.py # Listens on http://0.0.0.0:8008/mcp ``` **Docker:** ```bash # STDIO docker run -i --rm --env-file .env alanogic/mcp-odoo-adv # SSE docker run -p 8009:8009 --env-file .env alanogic/mcp-odoo-adv:sse # HTTP docker run -p 8008:8008 --env-file .env alanogic/mcp-odoo-adv:http ``` --- ## Claude Desktop Setup **Option 1: uvx (Recommended)** Add to `claude_desktop_config.json`: ```json { "mcpServers": { "odoo": { "command": "uvx", "args": ["--from", "odoo-mcp", "odoo-mcp"], "env": { "ODOO_URL": "https://your-instance.odoo.com", "ODOO_DB": "your-database", "ODOO_USERNAME": "your-username", "ODOO_PASSWORD": "your-password" } } } } ``` **Option 2: Python module** ```json { "mcpServers": { "odoo": { "command": "python", "args": ["-m", "odoo_mcp"], "env": { "ODOO_URL": "https://your-instance.odoo.com", "ODOO_DB": "your-database", "ODOO_USERNAME": "your-username", "ODOO_PASSWORD": "your-password" } } } } ``` --- ## Architecture Overview ### Two-Layer Design **1. MCP Server Layer** (`src/odoo_mcp/server.py`) - Built on FastMCP 2.12+ (MCP 2025-06-18 spec) - **2 universal tools**: `execute_method`, `batch_execute` - **3 resources**: models list, model schemas, record search - **3 prompts**: customer search, sales orders, exploration - Smart limits: DEFAULT_LIMIT=100, MAX_LIMIT=1000 - Pydantic models for type-safe responses **2. Odoo Client Layer** (`src/odoo_mcp/odoo_client.py`) - `OdooClient`: JSON-2 API client with JWT authentication - Automatic token refresh and session management - Singleton pattern via `get_odoo_client()` - Specialized methods wrapping `execute_method()` ### What Was Removed (v1.0 Simplification) **Removed 5 specialized tools** (~600 lines): - ❌ `search_employee` - Use `execute_method` with `hr.employee` - ❌ `search_holidays` - Use `execute_method` with `hr.leave` - ❌ `validate_before_execute` - Odoo's native errors are better - ❌ `deep_read` - Caused oversized responses - ❌ `scan_pending_crm_responses` - Too specific **Removed 2 prompts**: - ❌ `troubleshoot-operation` - Generic troubleshooting is better - ❌ `draft-crm-responses` - Replaced by COOKBOOK examples **Why?** - Power users can do everything with `execute_method` - Specialized tools were redundant/broken - Focus on documentation (COOKBOOK.md) over tools - Simpler codebase = more reliable --- ## The Two Tools ### 1. execute_method Universal access to the entire Odoo API. **Signature:** ```python execute_method( model: str, # Odoo model name (e.g., "res.partner") method: str, # Odoo method name (e.g., "search_read") args_json: str = "[]", # Positional arguments as JSON kwargs_json: str = "{}" # Keyword arguments as JSON ) ``` **Response:** ```python { "success": bool, "result": Any, # Method return value "error": str | None # Error message if failed } ``` **Smart Limits (Automatic):** - DEFAULT_LIMIT: 100 records (auto-applied if no limit specified) - MAX_LIMIT: 1000 records (hard cap on user requests) - Override: Set `"limit": N` in kwargs_json - Unlimited: Set `"limit": 0` (warns about massive datasets) **Examples:** ```python # Search customers execute_method( model="res.partner", method="search_read", args_json='[[["customer_rank", ">", 0]]]', kwargs_json='{"fields": ["name", "email"], "limit": 50}' ) # Create record execute_method( model="res.partner", method="create", args_json='[{"name": "New Customer", "email": "customer@example.com"}]' ) # Update record execute_method( model="res.partner", method="write", args_json='[[123], {"phone": "+1234567890"}]' ) # Delete record execute_method( model="res.partner", method="unlink", args_json='[[123]]' ) ``` ### 2. batch_execute Execute multiple operations atomically (all succeed or all rollback). **Signature:** ```python batch_execute( operations: List[dict], # List of operations to execute atomic: bool = True # Rollback all if any fails ) ``` **Operation Format:** ```python { "model": str, "method": str, "args_json": str, "kwargs_json": str } ``` **Response:** ```python { "success": bool, "results": List[dict], # Individual operation results "total_operations": int, "successful_operations": int, "failed_operations": int, "error": str | None } ``` **Example:** ```python batch_execute( operations=[ { "model": "res.partner", "method": "create", "args_json": '[{"name": "Customer A"}]' }, { "model": "sale.order", "method": "create", "args_json": '[{"partner_id": 123, "date_order": "2025-01-01"}]' } ], atomic=True # All or nothing ) ``` --- ## MCP Resources **1. `odoo://models`** - List all available Odoo models - Returns: `{"models": [{"name": "res.partner", ...}, ...]}` **2. `odoo://model/{model}/schema`** - Get model field definitions and metadata - Example: `odoo://model/res.partner/schema` - Returns: Complete field schema with types, descriptions **3. `odoo://search/{model}?{query}`** - Quick record search (limit=10) - Example: `odoo://search/res.partner?name=John` - Returns: Matching records with basic fields --- ## MCP Prompts **1. search-customers** - Guide for searching and filtering customers - Uses execute_method with res.partner **2. create-sales-order** - Step-by-step sales order creation - Uses batch_execute for related records **3. odoo-exploration** - Discovering models, fields, and relationships - Uses resources and execute_method --- ## Smart Limits System ### Why Limits? Without limits, searching `mail.message` could return **GBs of data** (thousands of emails). ### How It Works **Automatic Application:** ```python # User doesn't specify limit execute_method(model="mail.message", method="search_read") # → Automatically limited to 100 records # User requests too many execute_method(model="mail.message", method="search_read", kwargs_json='{"limit": 5000}') # → Capped at 1000 records, warning logged ``` **Override Limits:** ```python # Custom limit (within max) kwargs_json='{"limit": 500}' # OK, returns 500 # Unlimited (use with caution!) kwargs_json='{"limit": 0}' # WARNING: May return GBs kwargs_json='{"limit": false}' # WARNING: May return GBs ``` ### Efficient Querying Patterns **1. Specify Fields** ```python # ❌ Bad: Returns all fields execute_method(model="mail.message", method="search_read") # ✅ Good: Only needed fields execute_method( model="mail.message", method="search_read", kwargs_json='{"fields": ["date", "subject", "author_id"]}' ) ``` **2. Filter Aggressively** ```python # ❌ Bad: All messages execute_method(model="mail.message", method="search_read") # ✅ Good: Filtered by date and type execute_method( model="mail.message", method="search_read", args_json='[[ ["model", "=", "crm.lead"], ["date", ">=", "2025-01-01"], ["message_type", "=", "email"] ]]', kwargs_json='{"fields": ["date", "subject"]}' ) ``` **3. Use Pagination** ```python # Page 1 (records 0-99) execute_method( model="mail.message", method="search_read", kwargs_json='{"limit": 100, "offset": 0}' ) # Page 2 (records 100-199) execute_method( model="mail.message", method="search_read", kwargs_json='{"limit": 100, "offset": 100}' ) ``` **4. Count First** ```python # Check total count before fetching execute_method( model="mail.message", method="search_count", args_json='[[["model", "=", "crm.lead"]]]' ) # Returns: 1247 # Then paginate appropriately # 1247 records / 100 per page = 13 pages ``` --- ## Development Commands ### Code Quality ```bash # Format code black . isort . # Lint ruff check . # Type checking mypy src/ # Run all quality checks black . && isort . && ruff check . && mypy src/ ``` ### Building & Publishing ```bash # Build package python -m build # Publish to PyPI (requires credentials) twine upload dist/* # Build Docker images docker build -t mcp/odoo:latest -f Dockerfile . docker build -t mcp/odoo:sse -f Dockerfile.sse . docker build -t mcp/odoo:http -f Dockerfile.http . ``` ### Testing ```bash # Test transports (requires running Odoo instance) python test_transports_real.py # Test smart limits python test_limits.py ``` ### Debugging **Enhanced Logging (run_server.py):** ```bash # Logs to both stderr and ./logs/mcp_server_*.log python run_server.py # View real-time logs tail -f logs/mcp_server_*.log ``` **Environment Diagnostics:** ```bash # Prints all ODOO_* variables on startup python -m odoo_mcp # Shows: Python version, environment vars, available methods ``` --- ## Technical Details ### Odoo API Authentication **JSON-2 API (Odoo 19+)**: - Bearer token authentication - Automatic token refresh - Session management via cookies - Endpoints: `/api/v2/authenticate`, `/api/v2/call` **XML-RPC (Legacy fallback)**: - Basic authentication per request - Endpoints: `/xmlrpc/2/common`, `/xmlrpc/2/object` ### Domain Normalization The `execute_method` tool automatically normalizes domain parameters: **Supported Formats:** ```python # List format (Odoo native) [["field", "operator", "value"], ...] # Object format (AI-friendly) {"conditions": [{"field": "...", "operator": "...", "value": "..."}]} # JSON string '[["field", "=", "value"]]' # Single condition (auto-wrapped) ["field", "=", "value"] ``` **Normalization Process:** 1. Unwraps nested domains: `[[domain]]` → `[domain]` 2. Converts object format to list format 3. Parses JSON strings 4. Validates conditions (3-element lists or operators) 5. Preserves logic operators (`&`, `|`, `!`) ### Stateless Design - Each request creates fresh operation context - No persistent state between requests - Singleton `OdooClient` shared across requests - Clean request/response cycle ### Error Handling - Connection errors: `ConnectionError` with details - Authentication failures: `ValueError` with context - All errors logged to stderr - Detailed diagnostics on startup ### Python Version - **Required**: Python ≥3.10 - **Tested**: Python 3.10, 3.11, 3.12, 3.13 - **Configured**: `pyproject.toml` line 10 ### Dependencies ```toml [project] dependencies = [ "fastmcp>=2.12.0", # MCP framework (2025-06-18 spec) "requests>=2.31.0", # HTTP client ] [project.optional-dependencies] dev = [ "black", # Code formatter "isort", # Import sorter "mypy", # Type checker "ruff", # Fast linter "build", # Package builder "twine", # PyPI uploader ] ``` ### Package Structure ``` mcp-odoo-adv/ ├── src/odoo_mcp/ │ ├── __init__.py # Package init │ ├── __main__.py # Entry point (odoo-mcp command) │ ├── server.py # MCP server (2 tools, 3 resources, 3 prompts) │ └── odoo_client.py # Odoo JSON-2 + XML-RPC client ├── run_server.py # STDIO runner (enhanced logging) ├── run_server_sse.py # SSE runner (port 8009) ├── run_server_http.py # HTTP runner (port 8008) ├── pyproject.toml # Package config (setuptools) ├── fastmcp.json # MCP server metadata ├── README.md # User documentation ├── COOKBOOK.md # 40+ usage examples ├── CHANGELOG.md # Version history ├── DOCS/ │ ├── CLAUDE.md # This file │ ├── TRANSPORTS.md # Transport details │ └── LICENSE # MIT license ├── Dockerfile # STDIO container ├── Dockerfile.sse # SSE container ├── Dockerfile.http # HTTP container ├── .env.example # Environment template └── odoo_config.json.example # Config template ``` --- ## Common Patterns See **COOKBOOK.md** for 40+ detailed examples covering: ### Core Operations - Searching & reading records - Creating records - Updating records - Deleting records - Counting records ### Advanced Patterns - Many2one relationships - One2many relationships - Many2many relationships - Computed fields - Custom methods - Workflow actions - Batch operations - Error handling ### Efficiency Patterns - Pagination strategies - Field selection - Aggressive filtering - Count-before-fetch - Batch processing --- ## Troubleshooting ### Problem: uvx command not found **Solution:** ```bash # Install uv package manager curl -LsSf https://astral.sh/uv/install.sh | sh # Or with pip pip install uv ``` ### Problem: Connection timeout **Solution:** ```bash # Increase timeout (default: 30s) export ODOO_TIMEOUT=60 python -m odoo_mcp ``` ### Problem: SSL certificate verification fails **Solution:** ```bash # Disable SSL verification (not recommended for production) export ODOO_VERIFY_SSL=false python -m odoo_mcp ``` ### Problem: HTTP proxy required **Solution:** ```bash # Set HTTP proxy export HTTP_PROXY=http://proxy.company.com:8080 python -m odoo_mcp ``` ### Problem: Authentication fails **Check:** 1. ODOO_URL is correct (include https://) 2. ODOO_DB matches database name 3. ODOO_USERNAME has API access 4. ODOO_PASSWORD is correct (or use API key) **Debug:** ```bash # See authentication details on startup python -m odoo_mcp 2>&1 | grep -i auth ``` ### Problem: Smart limits blocking legitimate queries **Solution:** ```python # Override with explicit limit execute_method( model="your.model", method="search_read", kwargs_json='{"limit": 500}' # Up to 1000 ) # Or use pagination for page in range(10): execute_method( model="your.model", method="search_read", kwargs_json=f'{{"limit": 100, "offset": {page * 100}}}' ) ``` ### Problem: Need more than MAX_LIMIT (1000) records **Solution:** ```python # Use pagination in a loop all_records = [] offset = 0 limit = 1000 while True: result = execute_method( model="your.model", method="search_read", kwargs_json=f'{{"limit": {limit}, "offset": {offset}}}' ) records = result['result'] if not records: break all_records.extend(records) offset += limit ``` --- ## Best Practices ### 1. Always Specify Fields ```python # ❌ Bad: Returns all fields (slow, large payload) execute_method(model="res.partner", method="search_read") # ✅ Good: Only needed fields execute_method( model="res.partner", method="search_read", kwargs_json='{"fields": ["name", "email", "phone"]}' ) ``` ### 2. Filter Before Fetching ```python # ❌ Bad: Fetch all then filter in code all_partners = execute_method(model="res.partner", method="search_read") # Then filter in Python... # ✅ Good: Filter in Odoo execute_method( model="res.partner", method="search_read", args_json='[[["customer_rank", ">", 0], ["country_id.code", "=", "US"]]]' ) ``` ### 3. Use Batch Operations for Related Records ```python # ✅ Atomic: All succeed or all rollback batch_execute(operations=[ { "model": "res.partner", "method": "create", "args_json": '[{"name": "Customer"}]' }, { "model": "sale.order", "method": "create", "args_json": '[{"partner_id": 123}]' } ], atomic=True) ``` ### 4. Check Count Before Large Queries ```python # 1. Count first count_result = execute_method( model="mail.message", method="search_count", args_json='[[["model", "=", "crm.lead"]]]' ) total = count_result['result'] # Returns: 10000 # 2. Decide strategy based on count if total > 1000: # Use pagination pass else: # Fetch all pass ``` ### 5. Handle Errors Gracefully ```python result = execute_method(...) if not result['success']: print(f"Error: {result['error']}") # Handle error appropriately else: data = result['result'] # Process data ``` --- ## Version History ### v1.0.0-beta (Current) - Radical Simplification - **BREAKING**: Removed 5 specialized tools - **BREAKING**: Removed 2 prompts - **NEW**: Smart limits system (DEFAULT_LIMIT=100, MAX_LIMIT=1000) - **NEW**: Comprehensive COOKBOOK.md with 40+ examples - **NEW**: uvx support for zero-install usage - **IMPROVED**: Minimalist philosophy documentation - **FIXED**: Python version requirement (3.10+, was incorrectly 3.12+) ### v0.0.4 - Transport Support - Added SSE transport (port 8009) - Added Streamable HTTP transport (port 8008) - Enhanced logging to ./logs/ directory - Docker images for all transports ### v0.0.3 - JSON-2 API Migration - Migrated from XML-RPC to JSON-2 API (Odoo 19+) - JWT Bearer token authentication - Automatic token refresh - Legacy XML-RPC fallback support ### v0.0.2 - MCP 2025 Compliance - Upgraded to FastMCP 2.12+ - MCP 2025-06-18 spec compliance - Resource annotations - Output schemas ### v0.0.1 - Initial Release - Forked from tuanle96/mcp-odoo - Basic XML-RPC client - 7 specialized tools - STDIO transport only --- ## References - **MCP Specification**: https://spec.modelcontextprotocol.io/ - **FastMCP Framework**: https://gofastmcp.com - **Odoo API Documentation**: https://www.odoo.com/documentation/ - **Project Repository**: https://github.com/AlanOgic/mcp-odoo-adv - **Original Fork**: https://github.com/tuanle96/mcp-odoo --- *Odoo MCP Server Advanced v1.0.0-beta* *Two tools. Infinite possibilities. Full Odoo API access.*

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

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