Skip to main content
Glama
mohamedabuemira

Odoo MCP Bridge

Odoo MCP Bridge

An MCP server that sits outside Odoo and reaches it over the external API. Nothing is installed in the database, so it works on Odoo Online, where custom modules are impossible, as well as on Odoo.sh and on-premise.

One HTTPS endpoint, POST /mcp, stateless Streamable HTTP. Eight tools: odoo_list_models, odoo_describe_model, odoo_search, odoo_aggregate, odoo_create, odoo_update, odoo_delete, odoo_call_method.

Claude / ChatGPT  ──HTTPS + bearer──▶  this bridge  ──HTTPS + API key──▶  Odoo
                                       (your host)                  (Online / .sh)

Read this before deploying anything

The bridge holds an Odoo API key, and an API key carries every right its user has. The permission matrix in config.yaml runs in this process, in front of that key. It stops an assistant from doing something unintended. It does not stop anyone who obtains the key.

So the matrix is the second line, not the first. The first line is the Odoo side:

  1. Create a dedicated user — mcp.bot@company.com — not a person's account.

  2. Give it the narrowest groups that let it do the job. Never Settings.

  3. Write record rules on that user for anything sensitive.

  4. Clear its password so the account cannot be used to log in at all; the API key becomes the only way in.

  5. Generate the key: Preferences → Account Security → New API Key.

Whatever survives that is what the bridge is working with. Sell it that way, and the matrix on top is a genuine extra rather than a claim that collapses the first time someone looks at it.


Related MCP server: MCP Server for Odoo

Quick start

git clone <your repo> && cd odoo-mcp-bridge
pip install -r requirements.txt
cp config.example.yaml config.yaml     # then edit it

export MCP_TOKEN_DEMO=$(python -c "import secrets;print(secrets.token_urlsafe(32))")
export ODOO_API_KEY_DEMO=<the key from Odoo>
export MCP_CONFIG=config.yaml

uvicorn app.main:app --reload --port 8000

Check that Odoo actually answers:

curl localhost:8000/readyz

Then call a tool by hand before pointing an assistant at it:

curl -s localhost:8000/mcp \
  -H "Authorization: Bearer $MCP_TOKEN_DEMO" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call",
       "params":{"name":"odoo_list_models","arguments":{}}}' | jq

Tests:

pip install pytest && pytest -q

Connecting an assistant

The endpoint is https://<your-host>/mcp with an Authorization: Bearer <the token from config.yaml> header. Anything that speaks remote MCP with a custom header will connect — Claude's custom connectors, Claude Code (claude mcp add --transport http odoo <url> --header ...), and the ChatGPT developer-mode connector among them.

Two tokens are involved and they are easy to confuse:

Token

Who holds it

What it proves

token: in config.yaml

the assistant

may talk to this bridge, as this tenant

odoo.api_key

this bridge

may talk to that Odoo database, as the bot user

Never give the second one to an assistant.

Configuration

config.example.yaml is the reference; it is commented as documentation. The shape:

tenants:
  - id: acme
    token: env:MCP_TOKEN_ACME
    read_only: true                  # a good place to start
    odoo:
      url: https://acme.odoo.com
      db: acme
      transport: json2               # Odoo 19+. Use xmlrpc for 17/18.
      api_key: env:ODOO_API_KEY_ACME
    models:
      sale.order:
        read: true
        write: false
        domain: [["state", "not in", ["draft"]]]   # applied to reads AND writes
        methods: [action_confirm]                   # ORM verbs are refused here
        fields_deny: [margin]

Every secret is env:NAME. Nothing sensitive goes in the file, so the file belongs in git and the secrets belong in the platform's secret store.

Design points worth knowing:

  • A model not listed does not exist. No defaults, no inheritance.

  • domain is a boundary, not a filter. It is ANDed into every read and every id is resolved through it before any write, delete or method call. A caller holding an id from elsewhere still cannot touch a record the domain excludes.

  • The AND is explicit. Concatenating a caller's domain onto a policy domain is unsafe: a caller who sends ["|", leaf] turns the join into an OR and the policy stops applying. Domains are validated for balance and joined with an explicit &. There is a test for exactly this.

  • ORM verbs cannot be whitelisted as methods. write, unlink, copy and friends are refused at config-load time, so the matrix stays the only way to change data.

  • A business method needs write. Not read. A model marked read-only cannot confirm an order.

  • Deleting needs confirm: true as a separate acknowledgement.

  • Field masking (fields_deny / fields_allow) applies to reading, writing, describing, grouping and aggregating — not just to the row output.

Transports

Odoo version

transport

Notes

19+

json2

/json/2/<model>/<method>, bearer key, named args

17, 18

xmlrpc

also needs odoo.username

XML-RPC and JSON-RPC are deprecated as of Odoo 19 with removal announced for 20, so json2 is where new work should go.

Two consequences of JSON-2 shape what this bridge can offer. Each call is its own SQL transaction, so nothing here can make two calls atomic — anything needing atomicity has to be one business method on the Odoo side, which is why odoo_call_method exists. And on Odoo Online, the external API is documented as available on Custom plans only, not One App Free or Standard. Confirm the client's plan before promising anything.

The audit trail

One JSON object per line, on stdout and optionally to a file: who, which tool, which model, which records, how long, and the arguments with anything password-shaped redacted. Failed authentications are logged too, which is the first thing an auditor asks for.

It is evidence, not proof. A file on a host you control can be edited. If a client needs the stronger claim, chain the entries by carrying each line's SHA-256 into the next and publish the head digest somewhere you cannot reach. Until that exists, do not call it immutable.


Where to host it, free

The bridge is stateless and tiny, which is what makes free hosting realistic at all. Ranked for this specific job.

1. Google Cloud Run — best overall

Perpetual free tier (~2M requests/month), scales to zero, cold starts around a second, real HTTPS, secrets in Secret Manager. Needs a billing account on file, which is the only catch, and at this traffic the bill stays at zero.

gcloud run deploy odoo-mcp --source . --region us-central1 \
  --allow-unauthenticated --max-instances 2 \
  --set-env-vars MCP_CONFIG=/app/config.yaml \
  --set-secrets ODOO_API_KEY_ACME=odoo-key:latest,MCP_TOKEN_ACME=mcp-token:latest

Use us-central1: the free allowance is region-dependent. Keep --max-instances low and set a $1 budget alert — Cloud Run has no hard cap, it bills. See DEPLOY.md.

2. Oracle Cloud Always Free — best if you want it always on

A genuinely free-forever ARM VM (up to 4 cores / 24 GB). Always on, no cold starts, no request caps. You manage the box and the TLS certificate — Caddy makes that two lines. The right answer for anything a client depends on.

3. Hugging Face Spaces (Docker SDK) — easiest, no card

Free CPU tier, generous RAM, no credit card, secrets under Settings → Variables and secrets. Must listen on 7860, which the Dockerfile already does. Caveats: free Spaces are public and sleep after long inactivity, and it is built for demos rather than business APIs. Fine for testing and demos; do not put a client's production key there.

4. Render — free web service

512 MB, 0.1 vCPU, deploy from GitHub, render.yaml included. The problem for this use case is that free services spin down after 15 minutes idle and take up to a minute to wake — and an MCP client will report that as a failed connection, not as a slow one. Workable only with a pinger hitting /healthz every 10 minutes, which eats most of the monthly hour allowance.

5. Koyeb — free, scale-to-zero

One free service, 0.1 vCPU / 512 MB. Same cold-start caveat as Render.

No longer free: Heroku and Fly.io. Avoid: PythonAnywhere's free tier — outbound network is restricted to an allowlist, so it cannot reach a client's Odoo domain at all.

The honest version

Free tiers are for your demos and your own testing. The moment a paying client's API key is involved, a €4/month VPS or Cloud Run with a billing account is the responsible choice — not because free hosts are badly run, but because a shared free host with no SLA is a strange place to keep a credential that can read someone's whole database. Free tiers also disappear with a month's notice; two of the ones on every list a year ago are gone.

Whatever you pick, three things are non-negotiable: HTTPS, secrets in the platform's secret store rather than in the repo, and a long random token: per tenant.

Roadmap

  • OAuth 2.1 with PKCE, so each person authorises separately instead of sharing one bearer token

  • Hash-chained audit entries

  • outputSchema on the tool definitions

  • Per-tool rate limits rather than one bucket per tenant

Licence

Choose one before publishing. If this is going to be sold alongside services, AGPL-3 or a commercial licence; if it is a lead magnet, MIT.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables AI assistants to interact with Odoo ERP systems through XML-RPC communication. Provides access to Odoo models, records, methods, and data structures for comprehensive ERP integration.
    2
    MIT
  • A
    license
    Not graded
    quality
    Not graded
    maintenance
    Enables AI assistants to interact with Odoo ERP systems through natural language to search records, create entries, update data, and manage business operations. Supports secure authentication and configurable access controls for production environments.
    Mozilla Public 2.0
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables AI assistants to interact with Odoo ERP systems through natural language, allowing users to search, create, update, and manage business records like customers, products, and invoices across any Odoo instance.
    1
    Mozilla Public 2.0
  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables AI assistants to interact with Odoo databases via XML-RPC and JSON-RPC for performing CRUD operations and managing modules. It supports advanced features like domain-based searching, field metadata inspection, and administrative task execution.
    5 npm
    MIT