odoo-mcp-server
by rpl9717
README.md
# odoo-mcp-server
A minimal, security-first [MCP](https://modelcontextprotocol.io) server that
lets AI assistants (Claude Code, Claude Desktop, or any MCP client) read and
— optionally, behind explicit allowlists — write to an
[Odoo](https://www.odoo.com) 17 instance over XML-RPC.
Built and used in production-adjacent work: MRP cost analysis, simulated
purchase→manufacture→sale flows, and inventory cleanup driven entirely
through an AI assistant, with the blast radius controlled server-side.
## Design goals
- **Tiny, auditable surface**: one file (`server.py`, ~450 lines), stdlib +
the official `mcp` SDK only. No third-party business logic, no telemetry.
The only network destination is your `ODOO_URL`.
- **Read-only by default**: write tools are *not registered* unless
`ODOO_ENABLE_WRITES=1` is set. There is **no delete tool**, ever.
- **Allowlists all the way down**:
- models readable → `ODOO_ALLOWED_MODELS` (supports `prefix.*` wildcards)
- models writable → `ODOO_WRITE_MODELS` (subset of the above)
- fields writable per model → `ODOO_WRITE_FIELDS`
- workflow methods callable → `ODOO_ALLOWED_METHODS` (exact
`model:method` pairs)
- **Defense in depth**: these checks sit *on top of* the Odoo user's own
access rights, which remain the primary boundary. See
[SECURITY.md](SECURITY.md) for the full model.
## Tools
| Tool | Registered | Purpose |
|---|---|---|
| `odoo_search_read` | always | Search + read records (domain, fields, limit, order) |
| `odoo_read` | always | Read records by id |
| `odoo_search_count` | always | Count records matching a domain |
| `odoo_fields_get` | always | Introspect a model's fields |
| `odoo_create` | writes enabled | Create a record (model + field allowlists enforced) |
| `odoo_write` | writes enabled | Update records (model + field allowlists enforced) |
| `odoo_call_method` | writes enabled + method list non-empty | Run an allowlisted workflow method (confirm a PO, validate a picking, apply an inventory count, …) |
Responses are hygienic by default: capped record counts, long/base64 values
truncated, Odoo faults sanitized (no server tracebacks).
## Installation
Requires Python 3.12+ and an Odoo 16/17 instance reachable over XML-RPC.
```bash
git clone https://github.com/rpl9717/odoo-mcp-server.git
cd odoo-mcp-server
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt
cp .env.example .env
chmod 600 .env
# edit .env: ODOO_URL, ODOO_DB, ODOO_USER, ODOO_API_KEY, allowlists
```
In Odoo, create a **dedicated user** with a restricted access-rights group
and an API key (Settings → Users → My profile → Account security). Don't
run this with an admin key outside of a supervised test.
Sanity-check the configuration and connection:
```bash
.venv/bin/python server.py --check # auth + permission matrix
.venv/bin/python verify_mcp.py # end-to-end suite over real stdio
```
## Hooking it up to Claude Code
`.mcp.json` in your project (no secrets here — credentials live in `.env`
next to `server.py`):
```json
{
"mcpServers": {
"odoo": {
"command": "/path/to/odoo-mcp-server/.venv/bin/python",
"args": ["/path/to/odoo-mcp-server/server.py"]
}
}
}
```
One checkout can serve several databases: point `ODOO_ENV_FILE` at an
alternate env file per server entry.
## Configuration reference
See [.env.example](.env.example) — every variable is documented inline,
including the reasoning behind the field-level allowlist entries.
## Security
See [SECURITY.md](SECURITY.md): the layered security model, why a custom
server instead of the community options, engineering notes (XML-RPC thread
safety, `None`-marshaling quirk, driving TransientModel wizards over RPC),
and the verification checklist.
## License
[MIT](LICENSE)