mcp-server-odoo
README.md
# š mcp-server-odoo
A [Model Context Protocol](https://modelcontextprotocol.io/) server that gives AI
assistants full access to your Odoo ERP instance over XML-RPC.
No Odoo module installation required. Just point it at any Odoo 12+ instance.
```
š¤ AI Assistant (Claude Code, Claude Desktop, etc.)
ā MCP (stdio)
ā¼
š” mcp-server-odoo
ā XML-RPC
ā¼
š¢ Odoo Instance
```
## ā” Quick Start
No cloning required ā just run directly from GitHub with [uv](https://docs.astral.sh/uv/):
### š„ļø With Claude Code
```bash
claude mcp add odoo \
-e ODOO_URL=http://localhost:8069 \
-e ODOO_DB=mydb \
-e ODOO_USER=admin \
-e ODOO_PASSWORD=admin \
-- uvx --from git+https://github.com/altinkaya-opensource/odoo-mcp mcp-server-odoo
```
### š±ļø With Claude Desktop
Add to your `claude_desktop_config.json`:
```json
{
"mcpServers": {
"odoo": {
"command": "uvx",
"args": [
"--from",
"git+https://github.com/altinkaya-opensource/odoo-mcp",
"mcp-server-odoo"
],
"env": {
"ODOO_URL": "http://localhost:8069",
"ODOO_DB": "mydb",
"ODOO_USER": "admin",
"ODOO_PASSWORD": "admin"
}
}
}
}
```
### š Readonly Mode
Block all write operations by adding `READONLY_MODE=true`:
```bash
claude mcp add odoo \
-e ODOO_URL=http://localhost:8069 \
-e ODOO_DB=mydb \
-e ODOO_USER=admin \
-e ODOO_PASSWORD=admin \
-e READONLY_MODE=true \
-- uvx --from git+https://github.com/altinkaya-opensource/odoo-mcp mcp-server-odoo
```
## š ļø Tools
### š Read Operations
| Tool | Description |
| ------------------- | --------------------------------------------------------------------------------- |
| `search_records` | š Search any model with domain filters, field selection, pagination, and sorting |
| `read_record` | š Read a single record by ID with smart field selection |
| `get_record_count` | š¢ Count records matching a domain filter (lightweight, no data fetched) |
| `list_models` | š List all non-transient models available in the database |
| `get_model_fields` | šļø Inspect field definitions (type, required, help text, etc.) for any model |
| `read_group` | š Group records and compute aggregations (sum, avg, count) with date granularity |
| `save_binary_field` | š¾ Save a binary/image field from a record directly to a local file |
### āļø Write Operations
| Tool | Description |
| ---------------- | ------------------------------------------------------------------------------------- |
| `create_record` | ā Create a new record in any model |
| `update_record` | š Update fields on an existing record |
| `delete_record` | šļø Delete a record by ID |
| `copy_record` | š Duplicate an existing record with optional field overrides |
| `execute_method` | āļø Call any business method (e.g. `action_confirm`, `button_validate`, `action_post`) |
> š« Write tools are disabled when `READONLY_MODE=true`.
## ⨠Features
### š§ Smart Field Selection
When you don't specify which fields to fetch, the server automatically picks the most
useful ones. It scores fields based on type, importance patterns (`state`, `amount`,
`partner`, etc.), and excludes noisy fields like `message_ids`, binary blobs, and
computed non-stored fields. You always get `id`, `name`, `display_name`, and `active`.
Pass `fields=["__all__"]` to override and get everything.
### š Flexible Domain Parsing
Domains can be passed as JSON strings, Python repr strings, or native lists:
```python
# All of these work:
[["is_company", "=", true]]
'[["is_company", "=", true]]'
"[('is_company', '=', True)]"
```
### š
ISO 8601 Dates
Odoo's `"2025-06-07 21:55:52"` datetime strings are automatically converted to
`"2025-06-07T21:55:52+00:00"` for standard ISO 8601 output.
## š” Tool Usage Examples
**š Search for Turkish companies:**
```
search_records("res.partner", [["is_company", "=", true], ["country_id.code", "=", "TR"]], limit=20)
```
**š¢ Count open sale orders:**
```
get_record_count("sale.order", [["state", "=", "sale"]])
```
**š Read a specific product with all fields:**
```
read_record("product.product", 42, fields=["__all__"])
```
**ā
Confirm a sale order:**
```
execute_method("sale.order", "action_confirm", [42])
```
**š¦ Validate a stock picking:**
```
execute_method("stock.picking", "button_validate", [15])
```
**š° Post an invoice:**
```
execute_method("account.move", "action_post", [100])
```
**š Duplicate a sale order with a different partner:**
```
copy_record("sale.order", 10, default={"partner_id": 99})
```
**š¾ Save a product image to disk:**
```
save_binary_field("product.product", 42, "image_1920", "/tmp/product_image.png")
```
**š Total sales by partner:**
```
read_group("sale.order", "partner_id", domain=[["state", "=", "sale"]], fields=["amount_total:sum"])
```
**š
Monthly order counts:**
```
read_group("sale.order", "date_order:month", fields=["id:count"])
```
**š Average price by category:**
```
read_group("product.template", "categ_id", fields=["list_price:avg"])
```
**šļø Discover fields on a model:**
```
get_model_fields("sale.order", attributes=["string", "type", "required"])
```
## āļø Configuration
All configuration is done through environment variables:
| Variable | Required | Default | Description |
| ------------------------ | -------- | ------- | ----------------------------------------------- |
| `ODOO_URL` | ā
| | Odoo server URL (e.g. `http://localhost:8069`) |
| `ODOO_DB` | ā
| | Database name |
| `ODOO_USER` | ā
| | Username |
| `ODOO_PASSWORD` | ā
| | Password or API key |
| `READONLY_MODE` | | `false` | Set to `true` to disable all write operations |
| `ODOO_MCP_LOG_LEVEL` | | `INFO` | Log level (`DEBUG`, `INFO`, `WARNING`, `ERROR`) |
| `ODOO_MCP_LOG_FILE` | | stderr | Path to log file |
| `ODOO_MCP_DEFAULT_LIMIT` | | `10` | Default record limit for search operations |
Alternatively, create a `.env` file in the project root.
## š Requirements
- š Python >= 3.10
- š¦ [uv](https://docs.astral.sh/uv/) (recommended) or pip
- š¢ Access to an Odoo instance (12.0+) with XML-RPC enabled
## š License
AGPL-3.0-or-later
This server cannot be deployed
Maintenance
ActivityInactive
ResponsivenessNo issues