Tools reference¶
New in v1.6.0
Still 28 tools (25 with the standard profile).
odoo_versionadds apluginblock: the odoo-tools version that answered and, in Claude Code, its installed copy. Seeodoo_version.odoo_executeon Odoo 10 to 12: a key of an object passed positionally to a business method that is neither a field of the model nor an allowed context key is refused (rulecontext). Seeodoo_execute.
New in v1.5.1
No change to the list of tools: still 28 (25 with the standard profile).
The policy checks an object that odoo_execute passes positionally as a
context, because Odoo 10 to 12 take it as one (see
known limit 10). The tools run only in Claude
Code; in Cowork and Claude chat, /odoo-tools:odoo-doctor tells you where
to use them.
New in v1.5.0
- Permission policy: there are still 28 tools, but the default profile
standardlists 25:odoo_unlink,odoo_add_fieldandodoo_add_automationneed another profile. A hidden tool called by name explains why. See Permission policy. - Read-only mode no longer lists the 9 write tools.
odoo_reportis deprecated: it returns an error without calling Odoo and is removed in 2.0.0. Useodoo_record_documents.odoo_versionreturns apolicyblock;odoo_access_checkaddspolicy;odoo_list_modelsaddshidden_by_policy;odoo_online_profilereports a section the policy withholds inunavailable.- Credential-like fields are removed from reads, and the result names them
in
policy_withheld_fields. - The write tools state
destructiveHintin their annotations. odoo_connectionsno longer returns theuser:pass@of the URL.odoo_add_automationon Odoo 10 fills thekindtrigger field ofbase.action.rule.
New in v1.4.1
No changes to the tools: v1.4.1 is a documentation and diagnostics release. The tools are available only in Claude Code (see Where it works).
New in v1.4.0
- 28 tools (was 22): six for Odoo Online, useful on any Odoo 10-19. See Odoo Online.
odoo_executeaccepts a top-levelids, so the same call works on JSON-2 and on the legacy transports.odoo_read_groupusesformatted_read_groupfrom saas~18.4 on and keeps its arguments and its classic output.odoo_versionsigns in before answering and addstransport_noticeon Odoo 19+.
The MCP server exposes 28 tools (19 read and 9 write). How many are listed
depends on the permission profile: 25 under standard (the default), 19 under
readonly or with Read-only mode, and 28 under builder and full (see
Tools each profile lists). All operations on
models go through the compatibility layer: you can use modern model and
field names, and the server resolves them to whatever exists in the target
version. Unavailable fields are dropped with a dropped_fields warning instead
of failing the whole call. The tools work the same over JSON-2, JSON-RPC and
XML-RPC, except where noted.
Outside the full profile, credential-like fields (passwords, tokens, keys) are
removed from what reads return, and the result names them in
policy_withheld_fields.
Write tools
Nine tools modify data or can have side effects: odoo_create,
odoo_write, odoo_unlink, odoo_execute, odoo_translate_set,
odoo_add_field, odoo_add_automation, odoo_import_preview and
odoo_import. They are marked below with WRITES. With
Read-only mode (ODOO_READONLY=1) the server does not list them and
refuses all of them before touching Odoo. Outside that mode, the
permission policy decides each call on top of the Odoo user's
own rights (profile standard by default: no deletes, no Studio-style
changes, no outbound email except internal notes, no secrets). The public
core does not apply approval workflows (that lives in a private
governance layer). Use a least-privilege user and review every write. Their
MCP annotations state destructiveHint: false on odoo_create,
odoo_add_field and odoo_import_preview, true on the others.
In Odoo 10 to 12, before writing to account.move or account.invoice read
the warning in Compatibility: they are distinct models
(journal entries vs. invoices) and naming the wrong one can write over records
with different semantics. Name the correct model explicitly.
Summary¶
| Tool | Type | What it does |
|---|---|---|
odoo_search |
read | Searches and returns ids |
odoo_search_count |
read | Counts records |
odoo_read |
read | Reads records by id |
odoo_search_read |
read | Searches and reads in one call |
odoo_read_group |
read | Groups and aggregates on the server (GROUP BY) |
odoo_export_records_json |
read | Exports to JSON |
odoo_export_records_csv |
read | Exports to CSV |
odoo_version |
read | Version/edition/deployment/transport |
odoo_connections |
read | Active sessions |
odoo_list_models |
read | Lists models |
odoo_fields_get |
read | Fields of a model (cached) |
odoo_module_info |
read | Status of a module |
odoo_telemetry_preview |
read | Shows the opt-in telemetry payload (never sends it) |
odoo_translate_get |
read | Reads a translation |
odoo_report |
read (deprecated) | Returns an error without calling Odoo; use odoo_record_documents. Removed in 2.0.0 |
odoo_online_profile |
read | Series and line, deployment, transport, API keys and their expiry, Odoo Online limits |
odoo_api_catalog |
read | A model's methods with their parameter names |
odoo_access_check |
read | What the user may read, write, create and delete, and whether the user may export |
odoo_record_documents |
read | A record's attachments and an invoice's stored PDF |
odoo_create |
writes | Creates a record |
odoo_write |
writes | Updates records |
odoo_unlink |
writes | Deletes records (profiles builder and full) |
odoo_execute |
writes | Calls any method of the model |
odoo_translate_set |
writes | Sets a translation |
odoo_add_field |
writes | Adds a custom field (Studio-style; profiles builder and full) |
odoo_add_automation |
writes | Creates an automation (Studio-style; profiles builder and full) |
odoo_import_preview |
writes | Dry-runs an import with Odoo's importer, saving nothing |
odoo_import |
writes | Imports rows in a single atomic call |
Read and query¶
odoo_search¶
Searches a model and returns the matching ids.
| Parameter | Required | Type | Notes |
|---|---|---|---|
model |
yes | string | modern or historical name |
domain |
no | array | Odoo search domain |
limit / offset / order |
no | n/a | pagination and ordering |
odoo_search_count¶
Counts the records that match a domain (without fetching data).
odoo_read¶
Reads records by id, optionally limiting the fields.
| Parameter | Required | Type |
|---|---|---|
model |
yes | string |
ids |
yes | array of integers |
fields |
no | array of strings |
odoo_search_read¶
Searches and reads in a single call: the main query tool.
| Parameter | Required | Type |
|---|---|---|
model |
yes | string |
domain, fields, limit, offset, order |
no | n/a |
odoo_search_read {
"model": "account.move",
"domain": [["state","=","posted"]],
"fields": ["name","amount_total","invoice_date"],
"limit": 10, "order": "invoice_date desc"
}
odoo_read_group¶
Server-side aggregation (GROUP BY): the database groups and sums instead of
returning every row. It works on Odoo 10 to 19 and returns { model, groups }.
The model name is resolved; the names in fields and groupby are passed
as-is.
Up to saas~18.3 it uses the classic read_group. From saas~18.4 on it uses
formatted_read_group on every transport (19.0 deprecates the classic
read_group, and Odoo Online saas~19.1 to saas~19.4 do not have it), and keeps
the classic arguments and output: the <groupby>_count / __count keys,
__domain, __context for lazy grouping, date labels with __range, and
orderby. A field without a default aggregator is listed in dropped_fields:
ask for field:sum (or another operator) explicitly. From Odoo 17.0 each item
of fields must be field, field:agg or name:agg(field); any trailing
text is refused with a CompatError before Odoo is called.
| Parameter | Required | Type | Notes |
|---|---|---|---|
model |
yes | string | modern or historical name |
fields |
yes | array of strings | groupby fields plus aggregates, e.g. "expected_revenue:sum" |
groupby |
yes | array of strings | e.g. ["stage_id"] or ["invoice_date:month"] |
domain |
no | array | Odoo search domain |
limit / offset / orderby |
no | n/a | pagination and ordering of the groups |
lazy |
no | boolean | default true: groups only by the first groupby field |
odoo_read_group { "model": "crm.lead", "fields": ["stage_id","expected_revenue:sum"], "groupby": ["stage_id"] }
odoo_export_records_json¶
Exports the records that match a domain as a JSON array (read-only).
Same parameters as odoo_search_read. Returns { model, count, records }.
odoo_export_records_json { "model": "product.template", "fields": ["name","list_price"], "limit": 500 }
odoo_export_records_csv¶
Same as the previous one but returns CSV text. The many2one values are flattened
to their display name. Returns { model, count, columns, csv }.
odoo_export_records_csv { "model": "res.partner", "domain": [["customer_rank",">",0]], "fields": ["name","email","city"] }
Metadata and introspection¶
odoo_version¶
No parameters. Signs in and returns version, raw_version, edition,
deployment, the active transport (json2, jsonrpc, xmlrpc or
jsonrpc(auto)) and transport_notice: null, or on Odoo 19+ why /xmlrpc or
/jsonrpc is in use and how to move to JSON-2.
Since v1.5.0 it adds policy: the profile, the file, the digest, the hidden
tools, the openings, the warnings (W10 when the Odoo user is an administrator)
or the error that locked the policy. With the policy locked it is the only tool
available, and it answers without contacting Odoo. See
What the tools report.
Since v1.6.0 it adds plugin: the odoo-tools version that answered and, in
Claude Code, its installed copy, for example
{"name": "odoo-tools", "version": "1.6.0", "copy": "odoo-tools@transgenia-odoo-tools"}.
With two copies installed, it tells which one runs. It is there also when the
policy is locked. An answer without policy comes from a copy older than
1.5.0: the guided route and /odoo-tools:odoo-doctor stop there (see
Two copies of odoo-tools).
odoo_connections¶
No parameters. Lists the Odoo instances with an active session on the server.
Since v1.5.0 it strips from the URL it reports the user:pass@ of a basic-auth
gateway in front of Odoo, under every profile.
odoo_list_models¶
Lists models, optionally filtered by a name fragment. It leaves out the models
the permission policy allows nothing on, and returns how many in
hidden_by_policy.
| Parameter | Required | Type | Notes |
|---|---|---|---|
like |
no | string | fragment (case-insensitive) |
limit |
no | integer | default 200 |
odoo_fields_get¶
Returns the field definitions of a model (cached). It accepts historical or modern names.
| Parameter | Required | Type |
|---|---|---|
model |
yes | string |
attributes |
no | array of strings |
odoo_module_info¶
Installation status and metadata of a module by its technical name.
odoo_telemetry_preview¶
No parameters. Shows the exact opt-in telemetry payload you could share, or
opted_in: false when telemetry is off (the default; turn it on with
ODOO_TELEMETRY=opt-in). Read-only: it never transmits anything. Returns
{ opted_in, payload, note }. See Privacy.
odoo_translate_get¶
Reads the value of a field in a specific language for a record.
| Parameter | Required | Type | Notes |
|---|---|---|---|
model |
yes | string | |
id |
yes | integer | |
field |
yes | string | |
lang |
yes | string | e.g. es_MX, en_US, zh_CN |
odoo_report (deprecated)¶
Deprecated since v1.5.0; removed in 2.0.0
odoo_report always returns an error without calling Odoo. It used
the private method ir.actions.report._render_qweb_pdf, which Odoo refuses
over RPC on every version, so it could never work. It says so even before
a connection is configured, since it never asks for a session. To get a
PDF use odoo_record_documents, which returns
the documents Odoo already stored (for example an invoice PDF generated
when it was sent or printed).
It keeps its parameters (report_name, ids) so that an existing call gets
the error with its remedy instead of a validation error.
Write ¶
Every write goes through the permission policy: it is first decided from the arguments, before sign-in (Layer A), and then each Odoo call is checked again with its values and paths as it is sent (Layer B), including the records its x2many commands change. A refusal names the profile, the rule and what changes it.
odoo_create WRITES¶
Creates a record. Returns the new id.
| Parameter | Required | Type |
|---|---|---|
model |
yes | string |
values |
yes | object |
odoo_write WRITES¶
Updates records by id.
| Parameter | Required | Type |
|---|---|---|
model |
yes | string |
ids |
yes | array of integers |
values |
yes | object |
odoo_unlink WRITES¶
Deletes records by id. Under the default profile standard it is not listed
and deletes are refused: archive instead with
odoo_write { ..., "values": { "active": false } } where the model has an
active field. It is listed under builder (which deletes only on
customization models, unless a policy file opens unlink) and under full.
odoo_execute WRITES¶
Calls an arbitrary public method of the model (escape hatch). It can change data, so Read-only mode refuses it. It does not go through edition resolution, so use it with judgment.
Under the permission policy, odoo_execute is the call operation, plus the
class of the method (an unlink through odoo_execute is a delete). The
methods that read data (search_read, read_group, web_search_read,
onchange and others) are checked as reads, and the result of any other method
is redacted. The outbound methods (message_post to contacts,
message_subscribe, send_mail, and so on) are refused unless they are an
internal note (message_post with subtype_xmlid "mail.mt_note" and no
partner_ids) or a policy file lists them for that model. Only certain context
keys are accepted, and default_<field> values have their own limits: see
Context keys. Since Odoo 10 to 12 take an object
passed positionally after the method's parameters as the context, the policy
checks it with those rules too (see known limit 10):
pass the context by name, in kwargs. Since v1.6.0, on Odoo 10 to 12, a key of
an object passed positionally to a business method that is neither a field of
the model nor an allowed context key is refused (rule context), and when the
model's fields cannot be read, the call is refused (rule unverified). See
Built-in rules.
| Parameter | Required | Type | Notes |
|---|---|---|---|
model |
yes | string | |
method |
yes | string | |
ids |
no | array of integers | records of a record method (write, unlink, copy, action_*); leave it out for model methods such as search or create |
args |
no | array | positional arguments (execute_kw style; the first is the ids of a record method) |
kwargs |
no | object | named arguments, e.g. {"fields": ["name"]} |
The ids + kwargs form works on every transport, including JSON-2
(Odoo 19+), which accepts named arguments only. Positional args are named on
JSON-2 from the plugin's table of the methods it knows; if a model's override
uses other names, Odoo refuses it before running anything and the plugin
corrects the names or, in auto, sends that call over JSON-RPC/XML-RPC. Names
you pass in kwargs are never changed. To find the parameter names, use
odoo_api_catalog.
odoo_translate_set WRITES¶
Sets the translation of a field for a language. In Odoo 16+ it uses the native
update_field_translations API; in earlier versions it writes the field under the
language context. The compatibility layer picks the correct path.
| Parameter | Required | Type |
|---|---|---|
model |
yes | string |
id |
yes | integer |
field |
yes | string |
lang |
yes | string |
value |
yes | string |
odoo_translate_set { "model": "product.template", "id": 42, "field": "name", "lang": "en_US", "value": "Wireless mouse" }
odoo_add_field WRITES¶
Adds a custom field to a model (Odoo Studio-style, works on CE and EE). It creates a
manual ir.model.fields record (state='manual'); the name is forced to the
required x_ prefix. It needs the builder or full profile (under
standard it is not listed). See Low-code customization.
| Parameter | Required | Type | Notes |
|---|---|---|---|
model |
yes | string | target model, for example res.partner |
name |
yes | string | field name; x_ is prepended if missing |
label |
yes | string | visible label (field_description) |
field_type |
yes | string | char, text, html, boolean, integer, float, monetary, date, datetime, many2one, one2many, many2many, selection |
relation |
no | string | co-model for relational types |
relation_field |
no | string | inverse field for one2many |
selection |
no | array | for selection: list of [value, label] pairs |
required |
no | boolean | default false |
help |
no | string | help text |
odoo_add_field { "model": "res.partner", "name": "loyalty_points", "label": "Loyalty points", "field_type": "integer" }
odoo_add_automation WRITES¶
Creates an automated action (Studio-style) that runs server action code on a model
trigger. The Python code is validated against Odoo's safe_eval before being
sent (critical in Odoo online/SaaS). It adapts to the base.automation schema of
the target version (on Odoo 10, base.action.rule, whose trigger field is
named kind; since v1.5.0 the tool fills it). It needs the builder or full
profile (under standard it is not listed). See
Low-code customization.
| Parameter | Required | Type | Notes |
|---|---|---|---|
model |
yes | string | |
name |
yes | string | |
code |
yes | string | Python under safe_eval: use env, model, record(s), datetime...; assign action = {...} on the last line to return an action |
trigger |
no | string | on_create, on_write, on_create_or_write (default), on_unlink |
filter_domain |
no | array | optional domain that conditions the trigger |
odoo_add_automation {
"model": "crm.lead",
"name": "Flag hot leads",
"trigger": "on_create_or_write",
"code": "for rec in records:\n if rec.probability and rec.probability > 80:\n rec.write({'priority': '3'})"
}
safe_eval
The automation code runs under Odoo's safe_eval: no import, def,
class, return, with, no access to names/attributes with an underscore
or dunder, and no attribute assignment (rec.field = value compiles to
STORE_ATTR and is rejected; use rec.write({...})). The tool rejects those
constructs before sending. Detail in
Low-code customization.
Odoo Online (useful on any Odoo)¶
Six tools cover what an Odoo Online database makes hard: no custom Python, API access on Custom plans only, API keys that expire (18+), a new saas~X.Y line every few months, no PDF rendering over RPC, and imports as the sanctioned bulk path. They work on any Odoo 10-19 and degrade per version. On Odoo Online, pace bulk work: about 1 call per second, no parallel calls.
odoo_online_profile¶
No parameters. One read with: the series and line (19.0 or saas~19.2), the
deployment and how sure that detection is (deployment_confidence), the
transport, whether JSON-2 and Odoo's /doc-bearer catalog are usable, the
deprecation notice and when /xmlrpc and /jsonrpc disappear, the user and
companies, 2FA, the user's API keys with their expiry (names and dates only,
never key material), installed applications, imported data modules, Studio,
and the Odoo Online limits (Custom plan, about 1 call/s, 5-200 e-mails per
day, data modules only). Whatever a version lacks comes back as null.
Odoo 10-19; API keys from 14, expiry from 18. On 19.0 the module list needs an
administrator. Since v1.5.0, a section the permission policy withholds shows in
unavailable instead of making the tool fail.
odoo_api_catalog¶
A model's public methods with their parameter names in order, whether each one
is a model method (takes no ids) and whether it is read-only, for JSON-2 and the
ids + kwargs form of odoo_execute. On Odoo 19.0+ with an administrator's
API key it reads the database's own catalog (/doc-bearer, cached by ETag):
that model's exact names, custom and Studio methods included. Otherwise it
returns the plugin's built-in table: the base names from saas~18.4 on (a
model's override may rename a parameter) and no names (parameters: null) on
10.0 to saas~18.3, where odoo_execute uses positional args. source says
where they come from and note why. At most 100 methods; fields are in
odoo_fields_get.
| Parameter | Required | Type | Notes |
|---|---|---|---|
model |
yes | string | technical name, e.g. res.partner |
method |
no | string | fragment of the method name (case-insensitive) |
odoo_access_check¶
What the signed-in user (the owner of the configured credential) may do on a
model: read, write, create and delete, plus export_allowed
(base.group_allow_export group, Odoo 16+). By default it checks the model's
access rights; with ids on Odoo 18+ the record rules of those records count
too. It uses has_access on Odoo 18+ and check_access_rights on 10-17. It
changes nothing. Since v1.5.0 it also returns policy ({operation: true or
false} according to the permission policy) next to Odoo's own answer, with a
note in notes when Odoo allows an operation the policy refuses.
| Parameter | Required | Type | Notes |
|---|---|---|---|
model |
yes | string | technical name, e.g. sale.order |
operations |
no | array of strings | read, write, create, unlink; all four by default |
ids |
no | array of integers | up to 1000 ids for a record-level check (Odoo 18+) |
odoo_record_documents¶
Lists a record's attachments (id, name, MIME type, size in bytes, creation
date) and flags its main attachment and, on invoices (Odoo 17+), the invoice's
stored PDF, which a plain attachment search does not show. With
attachment_id and include_content: true it also returns that file in
base64, only if it belongs to that record and is at most max_bytes. It is the
way to get PDFs: these tools do not render reports (odoo_report is
deprecated). It returns documents Odoo already stored (e.g. the PDF generated
when the invoice was sent or printed). Odoo 10-19. Its internal calls to
ir.attachment are pinned to the record asked for (see
ir.attachment is pinned to its parent).
| Parameter | Required | Type | Notes |
|---|---|---|---|
model |
yes | string | e.g. account.move |
res_id |
yes | integer | the record's id |
attachment_id |
no | integer | one attachment of that record (an id from the listing) |
include_content |
no | boolean | default false; returns the file of attachment_id in base64 |
max_bytes |
no | integer | default 1048576 (1 MiB), at most 5242880 (5 MiB) |
limit |
no | integer | attachments listed, newest first; default 50, at most 500 |
odoo_import_preview WRITES¶
Dry-runs an import with Odoo's own importer (base_import, Odoo 16-19): it
returns the row errors and warnings Odoo would report and would_import, the
number of records it would create or update. It imports nothing: Odoo rolls
the run back, but it can still consume sequence numbers and fire effects
outside the database (e.g. webhooks of automated actions). That is why
Read-only mode refuses it. It needs JSON-RPC or JSON-2 (not XML-RPC).
| Parameter | Required | Type | Notes |
|---|---|---|---|
model |
yes | string | e.g. res.partner |
fields |
yes | array of strings | load() column names, e.g. ["name", "email", "country_id"]; field/id takes an external id, id holds the record's own external id (at most 100) |
rows |
yes | array of arrays of strings | one list of strings per record, in the order of fields (CSV cells: dates YYYY-MM-DD, numbers with . decimals, many2one by name or with field/id); at most 500 rows |
odoo_import_preview {
"model": "res.partner",
"fields": ["name", "email"],
"rows": [["ACME S.A.", "hola@acme.mx"], ["Beta S.A.", "info@beta.mx"]]
}
odoo_import WRITES¶
Imports rows into a model with Odoo's load(), in a single atomic call: every
row is saved or none is, and the errors say why. It returns the created or
updated ids, how many there are and Odoo's messages. An id column holds
external ids: a row whose external id already exists updates that record
instead of creating one. Same fields and rows as odoo_import_preview, at
most 500 rows per call. Odoo 12-19, over JSON-RPC or JSON-2.
Under the permission policy, imports need create and write on the model,
and each column is checked as a path (a column into a one2many creates and
writes lines on its comodel). Imports into res.partner that touch the
email of existing contacts follow the identity rule: see
Identity.
odoo_import { "model": "res.partner", "fields": ["name", "email"], "rows": [["ACME S.A.", "hola@acme.mx"]] }
Imports: confirm first, and never over XML-RPC
Neither tool runs by itself: the dry run does not import, and the agent
asks you before odoo_import. Odoo's XML-RPC cannot return the empty
values an import result carries, so a session on XML-RPC (Odoo 10-11, or
ODOO_TRANSPORT_PREF=xmlrpc) refuses them before sending anything, instead
of risking an import that was saved being reported as an error.
To understand how names are resolved across versions, see Cross-version compatibility.