MCP server by torina
odoo-mcp-server
Let AI talk to your Odoo ERP. Natively.
The most capable Model Context Protocol server for Odoo — onchange simulation, JSON-RPC, aggregations, safety controls, and a domain DSL that LLMs actually understand.
Quick Start • Examples • Tools • DSL • Safety • Config
Why this exists
Odoo stores everything — sales, inventory, accounting, HR, projects. But getting data out of it programmatically means fighting XML-RPC, memorizing Polish-notation domains, and writing boilerplate for every query.
odoo-mcp-server gives any MCP-compatible AI (Claude, GPT, Copilot, etc.) direct, safe, structured access to your Odoo instance. Ask questions in plain English. Get real answers from real data.
Comparison with mcp-server-odoo
| Feature | mcp-server-odoo | odoo-mcp-server |
|---|---|---|
| Tools | 7 | 14 |
| Protocol | XML-RPC only | JSON-RPC + XML-RPC fallback |
| Onchange simulation | ✗ | Full simulation — computed defaults before create |
| Aggregations | ✗ | read_group — sums, counts, grouped analytics |
| Domain syntax | Odoo native only | JSON DSL + native (LLM-friendly) |
| Dry-run / preview | ✗ | Yes — see what would happen before committing |
| Safety controls | Binary YOLO mode | Env tags + write scopes + confirmation gates |
| name_search | ✗ | Yes — fast partner/product lookup |
| Connection pooling | ✗ | httpx.AsyncClient with keepalive |
| Metadata caching | ✗ | LRU + TTL for field metadata |
Quick Start
pip install odoo-mcp-server
# or run directly
uvx odoo-mcp-server
Claude Desktop
Add to ~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"odoo": {
"command": "uvx",
"args": ["odoo-mcp-server", "--unsafe-bare"],
"env": {
"ODOO_URL": "http://localhost:8069",
"ODOO_API_KEY": "your-api-key-here",
"ODOO_DB": "mydb"
}
}
}
}
Claude Code
claude mcp add odoo -- uvx odoo-mcp-server --unsafe-bare
Set env vars in your shell or .env file.
Cursor / VS Code
Add to your workspace .vscode/mcp.json or settings:
{
"mcp": {
"servers": {
"odoo": {
"command": "uvx",
"args": ["odoo-mcp-server", "--unsafe-bare"],
"env": {
"ODOO_URL": "http://localhost:8069",
"ODOO_API_KEY": "your-api-key-here",
"ODOO_DB": "mydb"
}
}
}
}
}
Docker
docker run -e ODOO_URL=http://host.docker.internal:8069 \
-e ODOO_API_KEY=your-key \
-e ODOO_DB=mydb \
-p 8000:8000 \
odoo-mcp-server
Or use the included docker-compose.yml that spins up Odoo 17 + PostgreSQL:
git clone https://github.com/torina/odoo-mcp-server.git
cd odoo-mcp-server
docker compose up -d
What You Can Ask Your AI
"Show me all draft quotations over $5,000"
Uses odoo_search with JSON DSL: {"state": "draft", "amount_total": {"$gt": 5000}}
"Find the customer called Azure Interior"
Uses odoo_name_search on res.partner — one call, instant result.
"What's our revenue by sales team this quarter?"
Uses odoo_read_group — one aggregation call instead of fetching thousands of records.
"Create a quotation for 100 Widget Pros to Acme Corp"
Uses odoo_name_search to resolve IDs, then odoo_create with onchange simulation to auto-fill pricelist, payment terms, and fiscal position.
"Preview what would happen if I update partner 42's payment terms"
Uses odoo_write with dry_run=True — shows a field-by-field diff without touching your data.
"Describe the sale.order model"
Uses odoo_describe_model — returns every field with type, required/readonly flags, selection values, relations, and help text. Cached for 5 minutes.
Tools Reference
Discovery & Metadata
| Tool | What it does |
|---|---|
| odoo_list_models | List available models with optional text filter |
| odoo_describe_model | Full field schema — types, required, selection values, relations, help text |
| odoo_name_search | Fast lookup by display name (partners, products, etc.) |
Query
| Tool | What it does |
|---|---|
| odoo_search | Search with native domain OR simplified JSON DSL |
| odoo_read | Read specific records by ID, optional relation resolution |
| odoo_read_group | Aggregations (sum, count, avg) grouped by fields |
Mutation
| Tool | What it does |
|---|---|
| odoo_create | Create with automatic onchange simulation + dry-run preview |
| odoo_write | Update with field-by-field dry-run diff |
| odoo_unlink | Delete with bulk confirmation safety gate |
| odoo_onchange | Simulate field changes to preview computed values |
Admin
| Tool | What it does |
|---|---|
| odoo_health | Connection health, server version, user info, environment |
Domain DSL
Odoo's native domain syntax uses Polish-notation prefix operators that LLMs frequently get wrong. The JSON DSL is intuitive:
# Native Odoo (error-prone for LLMs)
["|", ("state", "=", "draft"), "&", ("amount_total", ">", 1000), ("partner_id.name", "ilike", "Acme")]
# JSON DSL (LLM-friendly)
{
"$or": [
{"state": "draft"},
{"$and": [
{"amount_total": {"$gt": 1000}},
{"partner_id.name": {"$ilike": "Acme"}}
]}
]
}
Operators
| DSL | Odoo | Example |
|---|---|---|
| $eq | = | {"state": {"$eq": "draft"}} |
| $ne | != | {"state": {"$ne": "cancel"}} |
| $gt / $gte | > / >= | {"amount": {"$gt": 1000}} |
| $lt / $lte | < / <= | {"amount": {"$lt": 500}} |
| $like / $ilike | like / ilike | {"name": {"$ilike": "acme"}} |
| $in / $nin | in / not in | {"state": {"$in": ["draft", "sent"]}} |
| $child_of / $parent_of | child_of / parent_of | {"parent_id": {"$child_of": 1}} |
Logical: $and, $or, $not
Both formats are always accepted. Every response includes the compiled_domain so the LLM learns native syntax over time.
Full reference: docs/domain-dsl.md
Safety Model
Environment Tags
| Tag | Behavior |
|---|---|
| dev (default) | All operations allowed, dry-run off |
| staging | All operations allowed, dry-run off |
| prod | Destructive ops require confirm=True, dry-run on by default |
Write Scopes
| Scope | What's allowed |
|---|---|
| read_only | Discovery + query tools only |
| write_safe (default) | Above + create, write, unlink |
| full | Everything including future admin operations |
Dry-Run
odoo_create and odoo_write accept a dry_run parameter:
- Production: defaults to
True— preview first, opt in to commit - Dev / Staging: defaults to
False— execute immediately - Always overridable with explicit
dry_run=Trueordry_run=False
Architecture
Claude / GPT / Copilot
|
| MCP protocol (stdio or streamable-http)
v
+---------------------+
| odoo-mcp-server |
| |
| FastMCP server |
| Safety gate |
| Domain DSL | ───> LRU + TTL cache
| Onchange engine |
| JSON-RPC client | ───> httpx async pool
+---------------------+
|
| JSON-RPC (or XML-RPC fallback)
v
Odoo 17 / 18
- FastMCP — tool registration, transport, lifespan management
- JSON-RPC primary protocol with connection pooling via
httpx.AsyncClient - XML-RPC fallback for legacy environments
- LRU + TTL cache — model metadata cached to avoid redundant round-trips
- Safety gate — scopes, env tags, dry-run, confirmation gates checked before every mutation
Configuration
All settings via environment variables (prefix ODOO_) or .env file:
| Variable | Required | Default | Description |
|---|---|---|---|
| ODOO_URL | Yes | — | Odoo instance URL |
| ODOO_API_KEY | * | — | API key authentication |
| ODOO_USERNAME | * | — | Username (if no API key) |
| ODOO_PASSWORD | * | — | Password (if no API key) |
| ODOO_DB | No | auto-detect | Database name |
| ODOO_ENV_TAG | No | dev | dev / staging / prod |
| ODOO_WRITE_SCOPE | No | write_safe | read_only / write_safe / full |
| ODOO_PROTOCOL | No | jsonrpc | jsonrpc / xmlrpc |
| ODOO_TRANSPORT | No | stdio | stdio / streamable-http |
| ODOO_DEFAULT_LIMIT | No | 20 | Default search result limit |
| ODOO_MAX_LIMIT | No | 200 | Maximum search result limit |
| ODOO_CACHE_TTL | No | 300 | Metadata cache TTL in seconds |
| ODOO_UNSAFE_BARE | No | false | Skip companion module check |
* Provide ODOO_API_KEY or both ODOO_USERNAME + ODOO_PASSWORD.
Development
git clone https://github.com/torina/odoo-mcp-server.git
cd odoo-mcp-server
# Install uv (if needed)
curl -LsSf https://astral.sh/uv/install.sh | sh
# Create venv and install with dev dependencies
uv venv --python 3.11
uv pip install -e ".[dev]"
# Run tests
python -m pytest tests/ -v
# Lint & format
ruff check src/
ruff format --check src/
# Type check
mypy src/
CI runs on Python 3.11, 3.12, and 3.13.
Roadmap
- Business actions —
call_methodwith allowlist, workflow transitions - Companion Odoo module (
mcp_bridge) — server-side allowlists, audit logging, scope enforcement - Messaging —
message_post, attachment upload/download - Reports — PDF/HTML rendering via MCP resources
- MCP Prompts — guided workflows (quote-to-cash, payment reconciliation, AR aging)
- Field redaction — configurable sensitive field masking
- Rate limiting — per-session token bucket
Contributing
Contributions are welcome! Whether it's a bug fix, a new tool, or better docs:
- Fork the project
- Create a feature branch (
git checkout -b feat/amazing-feature) - Write tests for your changes
- Make sure CI passes (
pytest,ruff,mypy) - Open a merge request
Please open an issue first for larger changes so we can discuss the approach.
License
MIT — use it in your company, your side project, wherever.
Built with MCP, FastMCP, and httpx.
If this saves you time, consider giving it a star.