Skip to main content
Glama
My Awesome MCP Server

My Awesome MCP Server

by fabiancasta

odoo-mcp-governed

An MCP server for Odoo where every tool call is policy-checked, risk-scored, and written to a tamper-evident audit log before it is allowed to touch the ERP.

Giving an AI agent write access to the system that holds your invoices, customers and stock is a straightforward thing to build and a hard thing to justify. The usual MCP server hands the model a set of tools and hopes. This one assumes the agent will eventually do something wrong — because its instructions changed, because it was prompt-injected, because its token leaked — and is built so that when that happens the damage is bounded and the evidence survives.

Status: v0.3 — all 19 tools, stdio and HTTP transports, admin panel and Docker deployment work end to end against Odoo 19. 577 tests, 15 of them clicking through the panel in a real browser.

What the operator gets

Panic switch

One click refuses every agent call on the next request. The panel and the audit log keep running, and it survives restarts until you resume.

Block one agent

Refuse every call from one token; the other agents keep working.

Undo with one click

Every create, write and delete is journalled with its old and new values. Undo writes the old values back, deletes what was created, or recreates what was deleted.

Human approval

Deletes, bulk changes or execute_method can wait for a person. The agent gets a request number; you approve in the panel (and get a webhook or Telegram message); the agent repeats the exact call once.

Live dashboard

Calls per hour, blocked attempts by kind, most used models, agent risk meters — refreshed every ten seconds.

Threshold console

Faders for throttle and block scores, delete bursts, call rate and risk memory, applied live and audited.

Permissions by app

Odoo's own models grouped by app with read/write/delete ticks. Models installed later show as "new — not reviewed" and stay blocked until you tick them.

Sandbox profiles

For test databases: model lists off and risk only observed, while the denylist, credential fields and audit stay on. Never on production.

Activity report

A printable "what your AI did" page per client and period — counts, refusals, changes — with no record values, ready to send as a PDF.

Several clients

Pin each agent's token to its client's Odoo profile; switching the active profile can never send it to another client's database.

Connect from Claude.ai or ChatGPT

OAuth 2.1 with PKCE (S256) and dynamic registration. The app sends you to the panel; you choose which agent it becomes and on which profile. It gets an ordinary registered token — revocable from the agent's tree. Redirects must be HTTPS (HTTP only to localhost); codes are hashed, single use, ten minutes.

MCP 2026-07-28

Stateless requests with server/discover; headers that must match the body (MCP-Protocol-Version, Mcp-Method, Mcp-Name); self-describing results. Clients from 2024-11-05 to 2025-11-25 keep working.

Content guards

Text addressed to an AI is caught in English, Spanish, Portuguese and French, through look-alike letters, full-width forms and invisible characters. Writing such text, or changing anything in the 30 minutes after reading some, waits for a person. At most 10 requests per agent wait at once.

No account takeover

The e-mail of a contact that belongs to a user — the address a password reset goes to — is never changed through MCP.

Nothing leaks sideways

Links to models the agent may not read (user_id → res.users…) come back empty; chatter, activities and followers follow the governance of the record they belong to; Odoo's errors reach the agent without the list of groups that would grant access.

Strict calls

Unknown arguments (sudo, uid…) are refused, not ignored; a method's context accepts only lang, tz, active_test and allowed_company_ids; execute_method with no allowlist refuses every method.

Idempotent creates

idempotency_key on create_record and upload_attachment: a retry returns the first result instead of a duplicate.

Two layers, in plain words

The Permissions page first shows what Odoo itself lets the profile's user do — asked of Odoo, per app — then three brakes on every agent: may change, may delete, a person approves deletes and bulk changes. Passwords, keys, certificates, modules, code, users and settings stay out whatever the switches say. Per-app and per-model lists are under Advanced.

Permissions per profile

One server, several Odoo (Spain, Colombia…): each profile can have its own lists, or follow the default ones. Each app gets one level in one click — none, read, change, delete — with a selector per model to fine-tune, a search box, and Odoo's system models kept apart.

Archive or delete agents

Revokes every token, blocks the agent and hides it; restore any time. The audit log keeps its history.

Clickable dashboard

Tabs (summary, Odoo profiles, policy). Every number, bar, refusal kind and model opens the audit log filtered to exactly those events.

Agent tree

Each agent — with a rule or only seen in the audit log — opens as a tree: profiles → what it can really read, change and delete there, model and tool limits, record scope, tokens, activity; with + to add.

Agent registry

Every token is listed and can be revoked on its own, or all of one agent's at once — no JWT_SECRET rotation. A rule per agent narrows the policy: fewer models or tools, read-only, a daily call quota, one pinned profile.

Record scope

A domain per model, e.g. a WhatsApp bot sees only res.partner id = {partner} and that customer's sale.order. {partner} comes from the token's signed variables, so one rule serves one token per customer. Searches are ANDed with the scope, ids outside it are refused, new records get the scope's values.

Injection warning

Record content is data. Text in it that addresses an AI ("ignore previous instructions…") still comes back, plus a separate warning to the agent and an audit entry.

Less approval fatigue

Approve all similar pending requests at once, or a kind of call (agent + tool + model) for 30 min, 2 h or 8 h, up to 50 uses — never for modules or code. A burst of similar requests notifies once.

Two-factor panel

ADMIN_TOTP_SECRET adds an authenticator-app code to the panel login; a code works once.

Related MCP server: OdooSurface MCP

How a call is handled

Tools cannot reach Odoo except through the governance engine. Every call passes three stages, in this order:

ToolCall
   │
   ├─ 1. Policy ──────── permitted at all?        → deny, and record the denial
   │                     (allowlists, per-operation
   │                      gates, volume ceiling)
   │
   ├─ 2. Detection ───── what does this look like  → signals, with weights
   │                     in context?               (8 behavioural detectors)
   │
   ├─ 3. Risk ────────── signals + this actor's     → allow / flag / throttle / deny
   │                     decayed recent history
   │
   └─ 4. Audit ───────── hash-chained record of what was asked,
                         what was decided, and what happened

Risk can narrow what policy permits. It can never widen it. A policy denial is final regardless of how low the risk score is, and no threshold setting can make a forbidden call allowed. If that relationship were bidirectional, tuning a number would be a way to grant permissions, and the allowlist would stop being the authority on what is allowed.

1. Policy — the preventive layer

Default deny. An empty ALLOWED_MODELS permits nothing, so every widening is something a human typed.

Read, write and delete are separate gates. Being allowed to read res.partner does not imply writing it; being allowed to write does not imply deleting. Most agent integrations need to read broadly and write narrowly, and a single "allowed models" list cannot express that.

execute_method — arbitrary method on arbitrary model, the widest surface in any Odoo API wrapper — is off by default, takes a method allowlist, and refuses the ORM write primitives outright. Calling unlink through it would otherwise delete records while never touching the delete gate.

Credential fields are matched by pattern, so the ones modules add under compound names (stripe_secret_key, unsplash_access_key, signup_token) are caught too, including on models installed after the server was configured. They are refused on every model, always. SENSITIVE_FIELDS adds patterns; it cannot remove the built-in floor (*password*, *secret*, *token*, *api_key*, *apikey*, *private_key*).

The denylist wins over any allowlist, * included. Besides the models that run code or hold credentials directly, it covers the indirect routes: res.config.settings, which reads and writes ir.config_parameter on your behalf, and module install and uninstall. Attachment content is only served by get_attachment, which checks the record the file belongs to; the generic read tools refuse it and leave it out. Reads with no field list also skip binary fields, so a contact does not arrive with ten copies of its photo.

2. Detection — the behavioural layer

Eight detectors, each looking for something a legitimate integration does once and almost never does repeatedly:

Detector

What it catches

allowlist_probing

Repeated denied calls, especially spread across models — the shape of someone mapping what is reachable. Weighted highest: it has no benign high-volume explanation.

delete_burst

Several deletes in quick succession. A loop that lost its exit condition, or an injected instruction being carried out.

mass_read

Large cumulative read volume. Each call is under the per-call cap and perfectly legal; only the sum reveals exfiltration.

write_amplification

One call writing or deleting a batch far above routine, but below the hard cap.

rate_anomaly

Volume spikes, both absolute and relative to this actor's own baseline. The relative check is what catches a quiet actor suddenly getting busy.

repeated_errors

A run of failures — an agent guessing at schema it does not have.

privilege_drift

An established actor touching a model it never has before.

off_hours

Activity outside the configured window. Weighted low on purpose.

Detectors describe; they never decide. Each emits weighted signals and the engine applies the thresholds. A heuristic that can refuse a call on its own is a heuristic that can take down production by itself — and relatedly, a detector that crashes is dropped with a note in the log rather than failing the call, because a bug in statistical code should not be an outage.

Each detector's docstring states how it produces false positives. A detector whose failure mode you cannot describe is one you will end up ignoring in practice. The defaults are tuned to start noisy and be tightened against real traffic, rather than start silent and hope.

3. Risk — turning signals into a decision

The score combines the current call's signals with the actor's recent ones, discounted by an exponential half-life (15 minutes by default). Patterns worth catching are built from individually unremarkable calls — the third delete is only alarming because of the first two — so history has to count. It has to decay just as much, or an actor that tripped a detector once is permanently degraded and the operator switches governance off.

Two kinds of signal are counted differently. A delete burst or an oversized write is new evidence each time, so those add up: three deletes in a minute score 25, the fourth 50, the fifth 75 and is refused. Off-hours, a high call rate, an unfamiliar model or a run of errors describe a state that every call in it reports again; those count once, at their strongest decayed weight. Summing them was how an ordinary assistant session used to get blocked within minutes. Calls refused because of risk do not feed the score that refused them, so an agent that keeps retrying still cools off, and the score is capped at 100.

Scoring is per-actor: one misbehaving agent does not raise another's risk.

Score

Decision

below throttle

allow, or allow_flagged if anything fired

≥ RISK_THROTTLE_THRESHOLD

throttle — the call succeeds after a real delay, turning a runaway loop into a trickle so a human has time to notice

≥ RISK_BLOCK_THRESHOLD

deny

4. Audit — the forensic layer

An append-only SQLite log where each event stores the hash of the one before it, so every hash depends on the entire history.

What the log deliberately does not contain: argument values. Odoo records hold personal and commercial data, and an audit log is the wrong place to accumulate a second copy of it. Stored instead are field names (useful, not sensitive) and a SHA-256 digest of the arguments, which lets you prove two calls were identical without being able to read either.

Each event does record the ids a call created, changed or deleted, as part of the hashed chain — "which record?" is the first question after an incident.

Denials are recorded, not dropped. A refusal is evidence, and the probing detector is built entirely out of refusals.

The values live somewhere else on purpose: the change journal (data/changes.db, 0600, CHANGE_JOURNAL) keeps the old and new values of every create, write and delete, linked to the audit event that sealed it, with credential fields stripped. The panel shows it under Changes. Keeping it separate means the audit log can be handed to an investigator without handing over the ERP.

Threat model, stated plainly

This is where most "tamper-proof log" claims quietly overreach.

  • Detected. Editing, deleting, reordering or splicing in a past event. The chain breaks at a named sequence number. Repairing the hash of the row you edited does not help — the next event still commits to the old one, so the break just moves forward one.

  • Detected only with AUDIT_HMAC_KEY set. An attacker who can read and write the database file and knows the record format can, with a plain SHA-256 chain, recompute every hash from the altered point forward and produce a chain that verifies. An HMAC chain closes this off for anyone who has the file but not the key.

  • Not detected by this module alone. An attacker who owns the running process has the key and can rewrite everything, head included. No single-host design fixes that. The answer is anchoring: periodically copy the head hash somewhere the server cannot reach back into. AuditLog.anchor exists for this and it is a two-line cron job, not a product.

  • Also not detected. Anything that bypasses this server and talks to Odoo directly. Governance here covers the MCP surface; claiming more would be dishonest.

Known limitation. The audit event is written once, after the outcome is known. If the process dies mid-call, the attempt leaves no trace. Recording intent before and outcome after would close that gap at the cost of doubling the log; for now the single event wins, and startup self-check verifies the log is writable so the common failure is caught before it matters.

Running it

Claude Desktop (stdio)

{
  "mcpServers": {
    "odoo": {
      "command": "odoo-mcp-governed",
      "args": ["stdio"],
      "env": {
        "ODOO_URL": "https://erp.example.com",
        "ODOO_DB": "mydb",
        "ODOO_USERNAME": "bot@example.com",
        "ODOO_API_KEY": "…",
        "ODOO_ENVIRONMENT": "staging",
        "ALLOWED_MODELS": "res.partner,sale.order",
        "AUDIT_HMAC_KEY": "…",
        "DATA_DIR": "/home/me/.local/share/odoo-mcp-governed"
      }
    }
  }
}

HTTP, with the admin panel

cp .env.example .env && chmod 600 .env    # fill in JWT_SECRET, ADMIN_PASSWORD, AUDIT_HMAC_KEY
mkdir -m 700 -p data
docker compose up -d                       # listens on 127.0.0.1:8020
odoo-mcp-governed token --actor claude     # one token per agent

The MCP endpoint is POST /mcp (POST / also works). The admin panel at /admin manages Odoo profiles (switching is instant, no restart), edits the policy live — tools on/off, model lists, execute_method — shows the token, and has an audit view with filters and a per-actor risk table. It contains no JavaScript at all and is served under a CSP that forbids scripts.

Administrative actions — logins, profile switches, policy changes — go into the same hash chain as agent calls. They are recorded but never risk-scored: an administrator switching a tool off at 03:00 during an incident must not be locked out by off-hours signals.

Command line

odoo-mcp-governed audit verify   # exit 1 if the chain is broken — cron it
odoo-mcp-governed audit anchor   # append the head hash to data/anchors.log
odoo-mcp-governed token --actor wabot --var partner=42 --hours 2   # registered, scoped
odoo-mcp-governed revoke --actor wabot   # refuse every token of one agent, no restart
odoo-mcp-governed totp-setup     # writes data/totp-setup.txt (0600): secret + otpauth:// URI

Production safeguards

Pointing a development setup at the live ERP is the most ordinary way to lose data, so there are two independent guards:

  • Unlabelled means production, and production is read-only. Every profile carries an environment. A profile without one — typically copied in from another tool — is treated as production, where creating, writing, deleting and execute_method are refused at the policy layer. Writing to a test database requires labelling it development or staging; writing to production requires ticking allow writes on that one profile, which the panel then shows in red.

  • PROTECTED_HOSTS lists hosts the server must never contact. A profile pointing at one (or any subdomain) cannot be saved or activated, and if the active profile turns out to point at one, the server starts with no profile rather than connect.

  • The policy is not the only barrier. A production profile whose Odoo user has Settings access — or whose access cannot be verified — refuses * model lists: if a policy rule were ever missing, nothing in Odoo would stop the agent. server_status warns about such a user on any profile, and reports writes_enabled with a plain-language note instead of raw flags.

Access levels

Each profile has one level:

Level

For

Reaches

Never

business (default)

Agents that work with the business: sales, collections, support, analysis

Business models in the lists; * means every business model

System models (ir.*, users, groups, settings, modules, payment providers…) unless named exactly

sandbox

Test data and controlled activities

Business models without lists; risk observed only

System models; the denylist

configurator

A consulting agent setting Odoo up for a client

The whole system: modules, settings, users, groups, views, automations

API keys, ir.attachment through the generic tools, credential fields, secret-looking system parameter values. Installing or removing modules and adding code wait for a human

Sandbox and configurator are refused on production profiles. Files are listed per record with list_attachments and fetched with get_attachment, both checked against the record they belong to; keys and certificates are never handed out, whatever they belong to.

A least-privilege production profile

The MCP policy and Odoo's own access control should each be enough on their own. For production:

Layer

Setting

Odoo user

A dedicated user (for example mcp_agent) with an API key, only the groups of the apps the agent works with, and neither Settings nor Technical

Models

Explicit ALLOWED_MODELS and WRITABLE_MODELS; DELETABLE_MODELS empty unless needed

Methods

ALLOW_EXECUTE_METHOD off, or a closed EXECUTE_METHOD_ALLOWLIST such as action_confirm,message_post (an empty one refuses every method)

Denylist

Kept as the floor, not relied on as the main control

Secrets

A fresh JWT_SECRET (rotating it revokes every token) and AUDIT_HMAC_KEY

Tokens

One per agent from the Agents page, then REQUIRE_REGISTERED_TOKENS=true

Several Odoo

Pin every agent to its profile, then REQUIRE_PINNED_PROFILE=true: an unpinned agent is refused instead of following the active profile

Panel

ADMIN_TOTP_SECRET set, HTTPS in front

Hardening carried over from day one

/docs and the OpenAPI schema are not served. Bodies are capped by counting bytes, so chunked uploads are capped too. 500s carry no detail. Security headers on every response, HSTS in HTTPS mode. Mcp-Session-Id is validated, never reflected. /mcp is rate-limited per actor, with a per-IP ceiling before authentication and a separate limit on failed authentications; batches are capped and every message in one counts. A foreign Origin header is refused (DNS rebinding). /health and login are limited per client IP, with X-Forwarded-For honoured only from configured proxies. Credentials and the audit database are written 0600 from the first byte, atomically. TLS to Odoo is verified by default. The container runs as a non-root UID on a read-only root filesystem with all capabilities dropped, no-new-privileges, memory/CPU/PID limits and its own network, publishing on 127.0.0.1 only. For publishing beyond localhost, deploy/firewall-docker.sh restricts the port through the DOCKER-USER chain, since Docker bypasses UFW, and deploy/egress-docker.sh stops the container from opening connections to anything but Odoo — not the internet, not the LAN, not a database on the host.

Full mapping, item by item: docs/SECURITY_BASELINE.md.

Red-teaming it

Unit tests prove each rule; redteam/ attacks a running server.

  • redteam/harness.py — fixed, repeatable attacks with an expected outcome each: the denylist and its indirect routes, credential fields (domains, dotted paths, ordering, nested specifications), attachment content, wizards and blocked methods, transport limits, probing, the panic switch, agent blocking, a delete burst, and the audit chain afterwards. Standard library only.

  • redteam/canaries.py — plants random secrets in a disposable Odoo where no agent may read them.

  • redteam/agent.py — an adversarial LLM, through any OpenAI-compatible API (Groq's free tier by default), told to steal the canaries, mass-delete and talk its way past the server. A canary in anything it received is a leak and fails the run; its opinion of how it did is not the verdict.

  • .github/workflows/redteam.yml — runs it on GitHub's machines against a throwaway Odoo 19 built by redteam/ci_world.sh: the harness on every push and pull request, the LLM attacker by hand with two model families. Actions are pinned by commit SHA and images by digest, with Dependabot proposing bumps.

The harness found a denial of service (a flood of bad tokens locked out valid agents on the same IP) and the canaries found a content leak (ir.attachment.index_content) on their first runs; both are fixed.

Tests

The suite has 577 tests (pytest tests/e2e drives the panel in Chromium with Playwright). The ones that matter are not the "it records events" kind — they are the adversarial ones:

python -m pytest tests/ -q
  • Tamper-evidence (test_audit_chain.py) reaches past the public API into the SQLite file to simulate an attacker with direct database access, because going through the public API would prove nothing — it has no way to modify a past event. Modification, deletion, hash repair, forged insertion and the unkeyed-vs-HMAC distinction are each asserted to be caught, at the right sequence number.

  • No permission leakage (test_policy.py) asserts read does not imply write, write does not imply delete, and that unlink and write cannot be laundered through execute_method even when explicitly allowlisted.

  • Signal, not noise (test_detectors.py) every detector is tested for silence on ordinary traffic as well as for firing on its pattern. One test runs a realistic workload through all eight and asserts zero signals — if that fails, every alert the system raises is noise.

  • The core invariant (test_engine.py) risk cannot widen policy; a crashing detector does not fail the call or inflate the score; actors are scored independently; a delete burst that starts permitted ends refused.

  • Concurrency is not a bypass (test_concurrency.py) ten deletes fired in parallel are judged as ten, not as ten first deletes each seeing an empty history. The test was checked by removing the per-actor lock: without it, all ten get through.

  • Never reaches Odoo (test_tools.py) tools run against a fake Odoo that records every request, so refusals are asserted as "Odoo saw nothing", not merely "an error came back".

  • Production stays read-only (test_production_guard.py) and protected hosts cannot be activated, whatever the profile file says.

  • HTTP hardening (test_http.py) alg: none, expired, wrong-issuer and expiry-less tokens; CSRF; chunked oversized bodies; secrets never in HTML; markup injected through audit data rendered inert.

The key security tests were mutation-checked — CSRF, the chunked body limit, secrets kept out of HTML, the domain password oracle and the per-actor lock: the protection was removed by hand and the test confirmed to fail, so that a passing test means something.

Tests drive a fake clock rather than real time, since every detector is a statement about when things happened.

Configuration

See .env.example. There are no insecure defaults: the server refuses to start in HTTP mode without ADMIN_PASSWORD and JWT_SECRET, TLS verification toward Odoo is on, and the model allowlist is empty.

Development

python -m venv .venv && . .venv/bin/activate
pip install -e ".[http,dev]"
pytest -q
ruff check src tests

A disposable Odoo 19 CE instance for testing (port 65010):

cd test-env && docker compose up -d
docker compose exec -T web odoo -d govtest -i base \
  --stop-after-init --db_host=db --db_user=odoo --db_password=odoo

Roadmap

  • Governance core — policy, detectors, risk scoring, hash-chained audit

  • Odoo client (JSON-RPC, real timeouts) and multi-profile hot swap

  • The 19 MCP tools, all routed through the engine

  • stdio transport (Claude Desktop) and HTTP transport (JWT)

  • Admin panel: profiles, live policy, tokens, audit log with risk view

  • Production safeguards: read-only by default, protected hosts

  • Hardened Docker image and compose file

  • Change journal with before and after values

  • One-click undo from the change journal

  • Panic switch and per-agent block

  • Human approval for destructive or bulk operations (panel, webhook, Telegram)

  • Live dashboard, threshold console, permissions by app, sandbox profiles

  • Activity report per client; tokens pinned to a profile

  • Per-profile model lists, with profile tabs in the panel

  • Approve from the phone (Telegram buttons / WhatsApp reply)

  • Two-phase audit (intent before the call, outcome after)

  • Agent registry: per-token revocation, per-agent rules and daily quotas

  • Record-level scope per agent, filled from signed token variables

  • Prompt-injection warning, grouped and standing approvals, TOTP for the panel

  • Signed off-box anchoring (today: audit anchor writes a file to ship)

Provenance

Written from scratch and MIT licensed. No code was copied from any existing Odoo MCP server. The implementation follows the published Odoo external API and the Model Context Protocol specification; the feature set and the security baseline come from the author's own experience running and hardening an internal Odoo MCP deployment, documented in docs/SECURITY_BASELINE.md.

License

MIT — see LICENSE.

Available Tools

19 tools
cache_statsC
Read-only

Metadata cache statistics.

Inputs: no named parameters.

call_onchangeA
Read-only

Simulate a form change and return the values Odoo would compute. Saves nothing.

Inputs: model (required), values (required), field_names, fields_spec.

create_recordC

Create a record.

Inputs: model (required), values (required), idempotency_key.

delete_recordB
Destructive

Delete one or more records. Irreversible.

Inputs: model (required), ids, record_id.

execute_methodB
Destructive

Call a public model method, e.g. sale.order.action_confirm. Restricted by policy.

Inputs: model (required), method (required), args, kwargs.

get_attachmentB
Read-only

Download an attachment as base64.

Inputs: attachment_id (required).

get_field_namesB
Read-only

Compact list of a model's field names with type and label.

Inputs: model (required).

get_model_fieldsB
Read-only

Field definitions of a model (type, relation, required...).

Inputs: model (required), attributes.

get_recordA
Read-only

Read one or more records by id.

Inputs: model (required), ids, fields, record_id.

get_reportB
Read-only

Render a PDF report (e.g. an invoice) for records. Returns base64.

Inputs: report_name (required), ids, record_id.

list_attachmentsA
Read-only

List the files attached to one record (name, type, size).

Inputs: model (required), record_id (required).

list_modelsB
Read-only

List the models this server allows access to, optionally filtered.

Inputs: limit, filter.

model_infoC
Read-only

Model description, Odoo access rights and what this server's policy permits.

Inputs: model (required).

search_countC
Read-only

Count records matching a domain.

Inputs: model (required), domain.

search_recordsC
Read-only

Search records with an Odoo domain and return the matching rows.

Inputs: model (required), limit, order, domain, +2 more.

server_statusB
Read-only

Server, active Odoo profile and governance status.

Inputs: no named parameters.

update_recordC

Update one or more existing records.

Inputs: model (required), values (required), ids, record_id.

upload_attachmentC

Attach a file (base64) to a record.

Inputs: model (required), filename (required), record_id (required), data_base64 (required), +2 more.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    B3.1/5.0

    Scored across 19 tools

    Disambiguation4/5

    Most tools target a distinct resource+action (search/create/get/update/delete record, attachment upload/list/download, report rendering), so selection is usually obvious. The main fuzziness is in introspection: get_model_fields, get_field_names, and model_info all describe a model's schema and differ only by output verbosity, and name_search overlaps search_records as a convenience shortcut.

    Naming Consistency4/5

    Strong snake_case convention with verb-first names (search_records, create_record, get_record, update_record, delete_record, list_attachments, get_attachment) used throughout. A few names deviate into noun-only or inverted forms (model_info, cache_stats, server_status, name_search, search_count), which slightly breaks the pattern but remains readable.

    Tool Count4/5

    19 tools is heavier than the ideal 3-15 range but justified for a generic Odoo API surface spanning CRUD, attachments, reports, and introspection. Some of the six metadata/status tools (get_model_fields vs get_field_names vs model_info, plus cache_stats/server_status) could be consolidated without losing capability.

    Completeness4/5

    Coverage is broad: full record CRUD, domain search and counts, name resolution, onchange simulation, attachment upload/list/download, PDF reports, and model/policy introspection—no dead ends for typical workflows. Minor gaps exist, such as deleting attachments, bulk import/export, and chatter/message operations, but agents can work around them.

    Maintenance

    ActivityMaintained
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • 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
      A
      quality
      B
      maintenance
      Enables AI agents to interact with Odoo ERP as the authenticated user, with tools for discovery, planning, and mutations bounded by user permissions.
      36
      65 npm
      2
      Apache 2.0
    • F
      license
      Not graded
      quality
      C
      maintenance
      Enables AI assistants to interact with Odoo ERP via the external API. It supports listing models, describing schemas, searching, aggregating, creating, updating, deleting records, and invoking business methods, with a configurable permission matrix for safety.
      -
    • A
      license
      Not graded
      quality
      C
      maintenance
      Enables AI assistants like Claude, ChatGPT, and Copilot to connect to Odoo ERP, providing tools to search, read, create, and update any Odoo model such as partners, sales orders, invoices, and products.
      15 npm
      AGPL 3.0