Skip to content

Tools reference

New in v1.6.0

Still 28 tools (25 with the standard profile).

  • odoo_version adds a plugin block: the odoo-tools version that answered and, in Claude Code, its installed copy. See odoo_version.
  • odoo_execute 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). See odoo_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 standard lists 25: odoo_unlink, odoo_add_field and odoo_add_automation need another profile. A hidden tool called by name explains why. See Permission policy.
  • Read-only mode no longer lists the 9 write tools.
  • odoo_report is deprecated: it returns an error without calling Odoo and is removed in 2.0.0. Use odoo_record_documents.
  • odoo_version returns a policy block; odoo_access_check adds policy; odoo_list_models adds hidden_by_policy; odoo_online_profile reports a section the policy withholds in unavailable.
  • Credential-like fields are removed from reads, and the result names them in policy_withheld_fields.
  • The write tools state destructiveHint in their annotations.
  • odoo_connections no longer returns the user:pass@ of the URL.
  • odoo_add_automation on Odoo 10 fills the kind trigger field of base.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_execute accepts a top-level ids, so the same call works on JSON-2 and on the legacy transports.
  • odoo_read_group uses formatted_read_group from saas~18.4 on and keeps its arguments and its classic output.
  • odoo_version signs in before answering and adds transport_notice on 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

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 { "model": "sale.order", "domain": [["state","=","sale"]], "limit": 5 }

odoo_search_count

Counts the records that match a domain (without fetching data).

odoo_search_count { "model": "account.move", "domain": [["state","=","posted"]] }

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_read { "model": "res.partner", "ids": [7, 9], "fields": ["name","email"] }

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_version {}

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_connections {}

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_list_models { "like": "sale", "limit": 50 }

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_fields_get { "model": "account.move", "attributes": ["string","type","required"] }

odoo_module_info

Installation status and metadata of a module by its technical name.

odoo_module_info { "name": "web_enterprise" }

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_telemetry_preview {}

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_translate_get { "model": "product.template", "id": 42, "field": "name", "lang": "en_US" }

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_create { "model": "res.partner", "values": { "name": "ACME S.A.", "email": "hola@acme.mx" } }

odoo_write WRITES

Updates records by id.

Parameter Required Type
model yes string
ids yes array of integers
values yes object
odoo_write { "model": "res.partner", "ids": [15], "values": { "phone": "+52 55 0000 0000" } }

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_unlink { "model": "res.partner", "ids": [999] }

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_execute { "model": "account.move", "method": "action_post", "ids": [128] }

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_online_profile {}

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_api_catalog { "model": "res.partner", "method": "write" }

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_access_check { "model": "sale.order", "operations": ["write"], "ids": [42] }

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_record_documents { "model": "account.move", "res_id": 128 }

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.