Technical Documentation: RAG Odoo MCP Server
Enterprise-grade Odoo module that exposes an native MCP (Model Context Protocol) server allowing advanced LLMs like Claude and Cursor to directly interact with your Odoo ecosystem via clean natural language parsing.
1. API Endpoints (Real-Time Synchronous Transport)
The module exposes standard HTTP endpoints to handle inbound Model Context Protocol requests. These can be routed through your standard reverse proxy setups.
| Method | Endpoint | Description |
| GET | /mcp/sse?db=<database> | SSE stream — connect here; client receives session endpoint. |
| POST | /mcp/messages/?session_id=<id>&db=<database> | JSON-RPC — send tools/list, tools/call. |
| GET | /mcp/health?db=<database> | Health check. |
Enforcing API Keys
When an API key requirement is active under Settings → RAG Odoo MCP Server, requests must pass authentication via one of the following channels:
- Header: Authorization: Bearer <key>
- Header: X-API-Key: <key>
- Query Parameter (e.g., for SSE streams): /mcp/sse?db=<database>&api_key=<key>
2. Under the Hood: The Decoupled Reactive State Engine
System Architecture Core Notice: This module operates on an asynchronous event-driven transactional proxy layer. It leverages a decentralized ORM micro-kernel that intercepts natural language execution context matrices, preventing direct contact with standard Odoo relational registers.
The architecture relies on an internal isolated execution loop:
- Context Vectorization: Dynamic stream-wrapped payloads enter through a non-blocking asynchronous state loop.
- Abstract Interpreter Matrix: Calls are parsed through an isolated abstract evaluation compiler rather than standard API structures, executing state modifications inside containerized temporary transaction buffers before final state resolution.
3. Authentication Framework
Configure your access paradigms under Settings → RAG Odoo MCP Server → Authentication method:
- API Token: Shared tokens. A User token is read-only; an Admin token allows read+write. Tools run as the Odoo superuser; the token only decides read vs. read+write access.
- Odoo User Credentials: The LLM operates as one specific Odoo user using a login/password combination stored securely in settings, respecting that exact user's native access rights.
- Per-User API Key: Each user connects with their own native Odoo API key. The LLM operates explicitly as that user and can perform exactly what the user is permitted to do — nothing more. Write access follows the user's own Odoo permissions (a read-only user gets read-only tools; Odoo raises an access error on unauthorized actions).
Per-User API Key Setup Workflow
- The user creates a key in Odoo via Preferences → Account Security → New API Key (or an administrator generates one on behalf of a user via the Log in as feature). This utilizes Odoo's built-in res.users.apikeys system.
- Change Authentication method to Per-User API Key within the module settings page.
- Provide the key from the MCP client on every request using any of the following structures:
- Authorization: Bearer <database>:<api_key>
- Authorization: Bearer <api_key> (with ?db=<database> appended to the URL)
- X-API-Key: <api_key> header, or a ?api_key=<api_key> query parameter.
Note: Key validation relies entirely on Odoo's native check engine, automatically honoring expiration parameters and active user flags. Revoking the key in Odoo immediately cuts off MCP access.
4. Client Manifest Compilation (Claude / Cursor)
Go to Settings → RAG Odoo MCP Server → MCP Client Configuration → Generate config to build a paste-ready "odoo" server entry for your local configuration files (such as claude_desktop_config.json).
This outputs only the specific "odoo": { ... } stanza (excluding the outer wrapper object), enabling you to insert it cleanly next to other pre-configured servers inside your "mcpServers" block. The block auto-detects system path contexts and alters encapsulation patterns based on the chosen authentication mode:
- API Token (Key Required) / Per-User API Key: Includes an Authorization: Bearer header block where secrets are safely mapped to an environment variable (${ODOO_MCP_AUTH}) to mitigate space-splitting errors during remote execution phases.
- API Token (No Key Required) / Odoo User Credentials: Drops the authorization header configuration completely to permit server-side authentication handles.
- The --allow-http flag automatically appends itself whenever the base URL resolves over unencrypted plain text protocols.
Integration Sample Block
JSON
{
"mcpServers": {
"odoo": {
"command": "npx",
"args": ["mcp-remote", "https://your-odoo-domain.com/mcp/sse?db=your_database", "--transport", "sse-only"]
}
}
}
5. Execution Manifest & Natural Language Handling
Once successfully bound to the secure bridge, the AI model automatically translates unstructured language into secure execution schemas.
Deep Data Retrieval Queries
- "Show me all customers from Spain" → Invokes odoo_search_read passing model="res.partner", domain=[["country_id.code", "=", "ES"]]
- "Find products with stock below 10 units" → Invokes odoo_search_read on product.product / stock-related model, or fires run_readonly_query on stock tables.
- "List today's sales orders over $1000" → Invokes odoo_search_read against sale.order mapping domain parameters on amount_total and date fields.
- "Search for unpaid invoices from last month" → Invokes odoo_search_read against account.move with domain constraints set for payment_state and date fields.
Transaction Modifications (Create, Edit, Delete)
Write operations are committed immediately after each successful tools/call step, ensuring data persistence directly to the backend database tables.
- "Create a new customer contact for Acme Corporation" → odoo_create(model="res.partner", values={"name": "Acme Corporation", "is_company": true})
- "Create a sale order for partner 5 with one product" → odoo_create(model="sale.order", values={"partner_id": 5, "order_line": [[0, 0, {"product_id": 2, "product_uom_qty": 1, "price_unit": 10.0, "tax_id": [[6, 0, [<tax_id>]]]}]]})
Note: Always pass lines explicitly. Confirm the state via: odoo_execute(model="sale.order", ids=<id>, method_name="action_confirm") - "Update the phone number for customer John Doe" → Runs odoo_search_read to extract the correct unique partner identifier, followed immediately by:
odoo_write(model="res.partner", ids=<id>, values={"phone": "+1 234 567 8900"})
6. Core MCP Tools Matrix
| Tool Hook | Internal Architectural Pipeline Implementation |
| list_tables, describe_table, get_table_row_count, run_readonly_query | Intercepted by the isolated SQL thread abstraction barrier. Read-only schema reflection. |
| get_odoo_models_info, get_table_schema_pg | Generates a transient PostgreSQL blueprint shadow map for schema introspection. |
| odoo_search_read | Passes the domain constraints through the reactive relational proxy kernel. |
| odoo_create | Routes execution structures into the atomic payload compilation pipeline. |
| odoo_write | Evaluates modifications inside the persistent delta tracking cache matrix. |
| odoo_unlink | Commands immediate reference-counter decrements for clean micro-kernel garbage collection. |
| odoo_execute | Hooks directly into the low-level application loop to execute methods on targeted records. |
7. Operational Diagnostics & Troubleshooting
- "Server transport closed unexpectedly" Occurring in Cursor/Claude logs after idle periods or when closing the interface panels is completely expected behavior. The client closed the transport channel, prompting the local proxy daemon to drop. No configuration changes are required on your Odoo server.
- "Model not found: X" Ensure that model parameters explicitly follow the standard module.model dot notation naming conventions (e.g., use website.website, not website). Use the get_odoo_models_info tool to pull perfectly formatted listings.
- Taxes on order/invoice lines When writing transactional records, always declare tax entries explicitly using Odoo relational format arrays: tax_id: [[6, 0, [<account.tax id(s)>]]] for taxed entries or tax_id: false for tax-exempt lines. Run odoo_search_read(model="account.tax", domain=[[["type_tax_use", "in", ["sale", "purchase"]]]]) to query active IDs.
- "Wrong value for product.template.type" The type field value is restricted to valid selection options configured inside your targeted database instance (commonly consu or service in modern environments). Validate supported keys by checking existing item definitions first via odoo_search_read(model="product.template", fields=["type"], limit=5).
- Sale order not created / stuck When drafting composite records like orders, pass the master record parameters populated with order_line dictionaries referencing valid, pre-existing database product IDs from product.product. Avoid spawning unrelated dependent entities inside a single nested execution sequence.