← Back to blog

Odoo 19.4 ships an MCP server. Here is what it exposes, and who can see it.

Odoo 19.4 has its own MCP server at /mcp, with no module to install. It exposes five tools and every one of them only reads. We ran saas-19.4 locally and asked the server itself: here is the tool list, the two new checkboxes that decide what lands in it, how it sits on Odoo's four layers of access rights, and why most Odoo databases will not see any of it for a long time.

Odoo now has its own MCP server. You point an AI client at /mcp, authenticate with an API key, and it works without installing a module from the App Store. Most of the writing about it so far describes it as an Odoo 20 feature, written from announcements. It arrived earlier than that, it does less than the announcements suggest, and most Odoo databases will not see it for a long while.

Five tools, and all five only read.

We checked this by running Odoo saas-19.4 locally and asking the server itself. Everything below comes from that instance, and you can reproduce all of it on yours.

Ask the server yourself

Start here, because the rest of this article is only worth reading if you can check it.

You need an API key with the MCP scope. Log in, open the user menu, go to the security tab, and generate a key. The scope dropdown is what differs from a normal key: pick MCP rather than the default. Then post a single JSON-RPC call to the endpoint, with your key as a bearer token in the authorization header, asking for the method tools/list.

Here is what comes back, with the input schemas and descriptions trimmed for length:

{
  "result": {
    "tools": [
      { "name": "ai_tool_get_fields",
        "annotations": { "readOnlyHint": true, "destructiveHint": false } },
      { "name": "ai_tool_get_models",
        "annotations": { "readOnlyHint": true, "destructiveHint": false } },
      { "name": "ai_tool_mcp_retrieve_initial_context",
        "annotations": { "readOnlyHint": true, "destructiveHint": false } },
      { "name": "ai_tool_read_group",
        "annotations": { "readOnlyHint": true, "destructiveHint": false } },
      { "name": "ai_tool_search",
        "annotations": { "readOnlyHint": true, "destructiveHint": false } }
    ]
  }
}

Five tools. Every one of them carries readOnlyHint: true.

What you get

ai_tool_mcp_retrieve_initial_context returns who you are, your timezone, your active company and the companies available to you. Odoo's own instructions tell the client to call it once at the start of a conversation, before anything else, because the other tools give wrong answers without it. Ask for "this quarter" in the wrong timezone and you get the wrong quarter.

The other four do the work. ai_tool_get_models lists the models you can reach, ai_tool_get_fields lists the searchable fields on one of them, ai_tool_search finds records, and ai_tool_read_group groups and aggregates.

That set is good at one thing: answering questions about your own data. "How many open quotations above 10k did we send this quarter, grouped by salesperson" works, and it works well. If that is what you wanted from AI in your ERP, Odoo's server does it, costs nothing extra, and takes five minutes to set up. Plenty of people want exactly that and can stop reading here.

What you do not get

Ask the endpoint to create something and it does not refuse the request. It says the tool is not there:

{ "result": { "content": [ { "type": "text",
  "text": "The tool ai_tool_create_records does not exist" } ],
  "isError": true } }

No create, no update, no delete, no method calls. Nothing that writes.

That is a decision rather than an oversight. The tool exists, it is registered, and it runs. It is simply not exposed to you.

You can see the size of that decision in the database. Odoo's AI tools are server actions, and each one has two independent switches: one for Odoo's own in-product chat, one for MCP. On a clean 19.4 install, twenty tools are switched on for the chat. Five are switched on for MCP. Among the sixteen the chat gets and you do not: ai_tool_create_records, ai_tool_update_records, ai_tool_run_view_action, ai_tool_web_search.

Same database, same permission model, different answer depending on which side the request comes from. Odoo's assistant writes. Yours reads.

The two new fields

Turn on developer mode and go to Settings, then Technical, then Server Actions. The AI tools are records in that list, on the model ir.actions.server, and two checkboxes decide how each one behaves over MCP.

"Available in MCP" decides whether the tool appears in tools/list at all.

"Readonly Tool" is what the server reports to your client as readOnlyHint.

Both are settings on a record, and that has consequences worth knowing about before you rely on either.

The read-only boundary is a checkbox

We ticked "Available in MCP" on ai_tool_create_records on our test instance. One field on one record. The next tools/list returned six tools instead of five, with the new one honestly labelled:

{ "name": "ai_tool_create_records",
  "annotations": { "readOnlyHint": false, "destructiveHint": true } }

So the five-tool list is a default, not a wall. If you administer an Odoo instance, your MCP surface is exactly as read-only as your server action configuration. Go and look at it rather than assuming.

This also means writing from an external client through Odoo's own MCP is a supported path, not a loophole. You just become the person who decides, and who owns the result.

The two fields do not check each other

"Readonly Tool" is a manual checkbox. Nothing forces it to match what the tool does.

We left ai_tool_create_records exposed and ticked "Readonly Tool" on it as well. The server then advertised this to the client:

{ "name": "ai_tool_create_records",
  "annotations": { "readOnlyHint": true, "destructiveHint": false } }

Then we called it, and it created a partner record. Id 7, sitting in res_partner, created by a tool that told our client it only reads.

The annotation is a declaration to the client, not an enforcement mechanism inside the server. Nothing validates it. If you tick that box wrong, your AI client believes it, and a client that trusts readOnlyHint to decide what needs human confirmation will skip the confirmation.

The shipped defaults are consistent, so this is a trap you have to build yourself. It is worth knowing it is there before you start customising.

How this sits on your access rights

The reassuring part: the MCP does not invent a new permission model.

We wrote a guide to how Odoo access rights actually work. The short version is that every action passes four gates: your groups, then ir.model.access at the model level, then record rules filtering which rows you see, then field-level access. One "no" stops the action.

An MCP request goes through all four, unchanged. The reason is in that guide: an API key grants no permissions of its own, it only says "I am this user". A key with the MCP scope is still just you.

We tested this with two keys on the same instance, one for an administrator and one for a plain internal user. Both got the same five tools back from tools/list. The tool list does not change per user.

What comes back through those tools does. Calling ai_tool_get_models as the administrator returned 88 models. The same call with the restricted user's key returned 56, and those 56 were a strict subset: not one model appeared for the restricted user that the administrator could not see. The AI has your permissions, and the four gates do the filtering exactly as they do in the interface.

The flip side is that there is no dial to turn it down. You cannot say "this person may do everything in the interface but only read customers through AI". The key is the user. To give the AI less, you create a second Odoo user with fewer rights and hand it that key, which is a real user on your bill. Skip that step and people reach for their own administrator account, and then the assistant has administrator rights over everything. Today's read-only tool list is a comfort with a shelf life attached to the defaults.

One detail worth noticing while you are in there. When we generated a key for the non-admin user without an expiry date, Odoo refused: "The API key must have an expiration date." The administrator's key had no such requirement. Non-admin keys expire, admin keys can live forever, which points the wrong way if convenience is driving your choice.

Who gets this, and when

This is the part that decides whether any of the above matters to you yet, and the version numbers actively mislead here.

Odoo Online and everything else run on separate tracks. Odoo Online rides the SaaS releases: 19.1, then 19.2, then 19.3, then 19.4. Odoo moves those databases forward themselves, several times a year, and you do not get a vote. Odoo.sh and self-hosted run the majors instead: 18.0, then 19.0, then 20.0.

And 19.0 does not have the MCP module. Not an older version of it. It is not there. We checked every branch: the module exists in saas-19.4 and in Odoo's development branch, and in none of the others, 19.0 included.

So saas-19.4 is not "newer than 19.0" the way the numbers suggest. They are parallel tracks. An Odoo.sh customer on 19.0 never becomes 19.4. They go to 20.0.

Which gives three quite different answers:

  • Odoo Online. You have it, or you get it within months, without doing anything.
  • Odoo.sh and self-hosted Enterprise. Nothing until the next major. The module sits in the development branch, so 20.0 should carry it, but 19.0 will not grow it.
  • Community. Never. It ships in Odoo's enterprise repository under a proprietary licence, and it depends on the paid AI module.

That middle line is the one people wave through. Going from 18.0 or 19.0 to 20.0 is an upgrade project: custom modules, testing, a window where things break, a budget and a date. If you are a partner with thirty clients, "we get MCP at 20" reads as "we get MCP after thirty upgrade projects". Most of those databases are a year or more away from an endpoint that only reads anyway.

Where we fit

We build Odoo MCP Pro, and the differences follow from the three things this article keeps circling.

It writes, and it goes past create and update. There is a tool called execute_method that calls any method on any model, so an assistant can run a wizard, post a payment or reconcile a bank line rather than describe one. That is also the tool that deserves the most care, which is why the next point exists.

It holds more than one Odoo. One connection covers every client instance and switches between them inside a conversation. Odoo's endpoint is one database by construction, and that is not an oversight they can patch: they sell per database.

It can scope below the user. Our governance module gives a key its own role and logs every call, so the assistant can be allowed less than the person holding the key. That is the dial Odoo's model does not have.

And it runs on 14 through 19, Community included, on any hosting. Given the release trains above, that is most of the Odoo that actually exists today.

One honest note to end on

All of the above describes a default, and defaults move. Odoo already has the write tools built and running in their own assistant. We turned one on from outside with a single field change, which tells you how far away the alternative is: exposing them is a configuration decision rather than an engineering project, so if it makes commercial sense for Odoo it can land in a point release.

The shape of the thing is harder to change. One database per endpoint, one edition, one release train. That part is a decision about a business model rather than a checkbox, and it is the difference worth paying attention to.