My Awesome MCP Server
Use it as a governed Odoo MCP server: agents can query and change ERP data only through policy, risk, and audit controls.
Read records by ID, search with Odoo domains, count matches, resolve names to IDs, and list allowed models.
Inspect model fields, field names, model metadata, Odoo access rights, and server/profile/governance status.
Create, update, and delete records; call permitted model methods; simulate onchanges.
Render PDF reports and upload/download attachments.
Enforce allowlists, denylists, sensitive-field blocking, separate read/write/delete gates, record scopes, and production read-only safeguards.
Risk-score calls with behavioral detectors and throttle or deny risky activity.
Warn on prompt-injection content.
Write every call to a tamper-evident hash-chained audit log and journal changes for one-click undo.
Require human approval for deletes, bulk changes, and
execute_method.Operate a panic switch, per-agent block, token registry/revocation, live dashboard, threshold console, and admin panel.
Support stdio and HTTP/OAuth transports plus Docker deployment.
Provides governed MCP access to Odoo ERP, allowing AI agents to read, write, and delete records across allowed Odoo models such as res.partner and sale.order, with policy checks, risk scoring, throttling/blocking, and tamper-evident audit logging. It also supports controlled access to execute_method with strict allowlists and refuses ORM write primitives and credential fields.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@My Awesome MCP Server show the last 10 sale orders for Acme and their totals"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
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 |
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 |
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 ( |
Strict calls | Unknown arguments ( |
Idempotent creates |
|
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 |
Record scope | A domain per model, e.g. a WhatsApp bot sees only |
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 |
|
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 happenedRisk 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 |
| Repeated denied calls, especially spread across models — the shape of someone mapping what is reachable. Weighted highest: it has no benign high-volume explanation. |
| Several deletes in quick succession. A loop that lost its exit condition, or an injected instruction being carried out. |
| Large cumulative read volume. Each call is under the per-call cap and perfectly legal; only the sum reveals exfiltration. |
| One call writing or deleting a batch far above routine, but below the hard cap. |
| Volume spikes, both absolute and relative to this actor's own baseline. The relative check is what catches a quiet actor suddenly getting busy. |
| A run of failures — an agent guessing at schema it does not have. |
| An established actor touching a model it never has before. |
| 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 |
|
≥ |
|
≥ |
|
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_KEYset. 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.anchorexists 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 agentThe 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:// URIProduction 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 asproduction, where creating, writing, deleting andexecute_methodare refused at the policy layer. Writing to a test database requires labelling itdevelopmentorstaging; writing to production requires tickingallow writeson that one profile, which the panel then shows in red.PROTECTED_HOSTSlists 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_statuswarns about such a user on any profile, and reportswrites_enabledwith 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; | System models ( |
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, |
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 |
Models | Explicit |
Methods |
|
Denylist | Kept as the floor, not relied on as the main control |
Secrets | A fresh |
Tokens | One per agent from the Agents page, then |
Several Odoo | Pin every agent to its profile, then |
Panel |
|
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 byredteam/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/ -qTamper-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 thatunlinkandwritecannot be laundered throughexecute_methodeven 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 testsA 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=odooRoadmap
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 anchorwrites 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 toolscache_statsCRead-only
Metadata cache statistics.
Inputs: no named parameters.
call_onchangeARead-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_recordBDestructive
Delete one or more records. Irreversible.
Inputs: model (required), ids, record_id.
execute_methodBDestructive
Call a public model method, e.g. sale.order.action_confirm. Restricted by policy.
Inputs: model (required), method (required), args, kwargs.
get_attachmentBRead-only
Download an attachment as base64.
Inputs: attachment_id (required).
get_field_namesBRead-only
Compact list of a model's field names with type and label.
Inputs: model (required).
get_model_fieldsBRead-only
Field definitions of a model (type, relation, required...).
Inputs: model (required), attributes.
get_recordARead-only
Read one or more records by id.
Inputs: model (required), ids, fields, record_id.
get_reportBRead-only
Render a PDF report (e.g. an invoice) for records. Returns base64.
Inputs: report_name (required), ids, record_id.
list_attachmentsARead-only
List the files attached to one record (name, type, size).
Inputs: model (required), record_id (required).
list_modelsBRead-only
List the models this server allows access to, optionally filtered.
Inputs: limit, filter.
model_infoCRead-only
Model description, Odoo access rights and what this server's policy permits.
Inputs: model (required).
name_searchARead-only
Find records by display name — the quick way to resolve a name to an id.
Inputs: model (required), name, limit, domain, +1 more.
search_countCRead-only
Count records matching a domain.
Inputs: model (required), domain.
search_recordsCRead-only
Search records with an Odoo domain and return the matching rows.
Inputs: model (required), limit, order, domain, +2 more.
server_statusBRead-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
Scored across 19 tools
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.
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.
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.
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
Related MCP Connectors
Odoo ERP for AI agents: hosted OAuth endpoint, gated writes, one endpoint for every instance.
Read, write and run Odoo actions from any AI app, as each user, with approval rules per colleague.
Connect AI to Odoo ERP 14-20: search, create, update and manage records in natural language.
Authorize AI agent tool calls before they run: policy-as-code, human approval, signed receipts.
Related MCP Servers
- AlicenseNot gradedqualityNot gradedmaintenanceEnables 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
- AlicenseAqualityBmaintenanceEnables AI agents to interact with Odoo ERP as the authenticated user, with tools for discovery, planning, and mutations bounded by user permissions.3665 npm2Apache 2.0
- FlicenseNot gradedqualityCmaintenanceEnables 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.-
- AlicenseNot gradedqualityCmaintenanceEnables 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 npmAGPL 3.0