Skip to main content
Glama
rpl9717

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)