← Blog
odoomcpai-agentsjson-rpc

Building an Odoo MCP Server for AI Agents

The Model Context Protocol lets AI agents call tools. Here's how an Odoo MCP server exposes your ERP to Claude and other LLMs — safely, through a proxy.

ODXProxy Team · Jul 5, 2026 · 7 min read

Building an Odoo MCP Server for AI Agents — ODXProxy blog cover

Your Odoo instance is where the real work lives — customers, quotations, invoices, stock moves. The new generation of AI agents (Claude, IDE assistants, chat copilots) is very good at deciding what to do, but it has no hands: it can't read a partner record or confirm a sale order unless you give it a tool to do so. An Odoo MCP server is that tool layer. This guide explains what the Model Context Protocol is, how an Odoo MCP server fits in front of your ERP, and why routing every agent call through a proxy — rather than handing an LLM your Odoo credentials directly — is the pattern you actually want in production.

What MCP is, in one paragraph

The Model Context Protocol (MCP) is an open standard for connecting AI applications to external tools and data. An MCP server advertises a set of tools (each with a name, a description, and a JSON Schema for its inputs); an MCP client — Claude Desktop, an IDE assistant, or your own agent runtime — connects to that server, discovers the tools, and lets the model call them. The wire format is JSON-RPC 2.0, and the transport is typically stdio (for a local process) or streamable HTTP (for a remote server). If you have ever written a function-calling integration by hand, MCP is the standardized, reusable version of that: build the server once, and any MCP-aware client can use it.

That makes MCP a natural fit for Odoo. Instead of teaching every AI product how to speak execute_kw, you expose a handful of well-described Odoo tools once, and every agent that speaks MCP can read and write records on your behalf.

The architecture: client → MCP server → proxy → Odoo

An Odoo MCP server sits in the middle of a short chain. Nothing about it talks to Odoo's XML-RPC layer directly; it translates MCP tool calls into ODXProxy's unified JSON-RPC request shape, and the proxy does the talking to Odoo.

An MCP client calls tools on the Odoo MCP server, which sends JSON-RPC to ODXProxy, which runs execute_kw on Odoo

Each hop has one job:

  • MCP client — hosts the model, discovers tools, and decides which to call with what arguments.
  • MCP server — maps each tool call onto a single ODXProxy action (search_read, create, call_method, …), holds the proxy URL and keys, and shapes the response back into something the model can read.
  • ODXProxy — authenticates the request, validates the action against its allowlist, forwards it to the target Odoo instance, and returns a JSON-RPC 2.0 envelope.
  • Odoo — runs the actual ORM call.

The odxproxy-mcpserver project is exactly this middle box: an MCP server that wraps the proxy's actions as MCP tools so an AI agent can operate an Odoo instance without ever holding raw Odoo credentials.

What the tools look like

ODXProxy exposes nine actions — search_count, search, read, fields_get, search_read, create, write, unlink, and call_method. An Odoo MCP server maps these onto MCP tools with descriptions written for a model to read. A search_read tool, for example, might be advertised like this:

{
  "name": "odoo_search_read",
  "description": "Search Odoo records matching a domain filter and read back selected fields in one round trip. Use this to look up, list, or filter records such as customers, sale orders, or products.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "model": { "type": "string", "description": "Odoo model name, e.g. res.partner" },
      "domain": { "type": "array", "description": "Odoo search domain, e.g. a filter on is_company" },
      "fields": { "type": "array", "items": { "type": "string" } },
      "limit": { "type": "integer" }
    },
    "required": ["model", "domain", "fields"]
  }
}

When the model decides to call odoo_search_read, the MCP server receives those arguments and turns them into a single ODXProxy request. Under the hood, that call becomes an ordinary POST /api/odoo/execute with action: "search_read":

{
  "id": "mcp-42",
  "action": "search_read",
  "model_id": "res.partner",
  "params": [[["is_company", "=", true]]],
  "keyword": { "fields": ["name", "email"], "limit": 20 },
  "odoo_instance": {
    "url": "https://erp.example.com",
    "db": "prod",
    "user_id": 2,
    "api_key": "<the Odoo user API key>"
  }
}

Note the two levels of shaping. The MCP server owns the mapping from tool argument (domain) to wire field (params is a JSON array, so the domain is nested one level: [[domain]]), and it owns the odoo_instance block entirely — the model never sees or supplies Odoo credentials. That is the whole point of putting the server in the middle.

For anything that isn't plain CRUD — confirming a sale order, posting an invoice, running a custom method — the server maps the tool onto call_method with a non-empty fn_name. The mechanics of that action are covered in calling custom Odoo methods with call_method; an MCP server is just an automated caller of the same endpoint.

Why a proxy sits between the agent and Odoo

You could let an agent hit Odoo's XML-RPC endpoint directly. You shouldn't, and the reasons are the same ones that make a gateway valuable for any integration — they're just sharper when the caller is a non-deterministic model.

  • Credential isolation. There are two distinct secrets in this stack, and they must never be conflated. The proxy's own x-api-key header authenticates the MCP server to ODXProxy; the Odoo user's key lives in odoo_instance.api_key inside the request body. The model never touches either — it only ever names a tool and its arguments. A prompt-injected agent can't leak a key it was never given.
  • A fixed action allowlist. The proxy only forwards its nine known actions; anything else comes back as HTTP 400 with JSON-RPC code -32001. Even if a tool description is misread, the surface area an agent can reach is bounded by design.
  • Per-request routing and timeouts. Because the target Odoo instance is named per request, one MCP server can front multiple databases, and an optional x-request-timeout header caps how long any single agent-driven call may run upstream.
The two-key model is the single most important thing to get right when you wire an agent to Odoo. x-api-key is the proxy's key; odoo_instance.api_key is the Odoo user's key. Keep them separate, and keep both out of the model's context.

The HTTP 200 trap applies to agents too

There's one protocol rule that matters even more once an autonomous agent is in the loop: an HTTP 200 response can still carry an error. Odoo's own logic errors — a validation rule, an access-right denial, a UserError raised on purpose — come back with a 200 status and a populated error object. Only proxy-layer failures (bad key, unknown action, upstream timeout) use non-200 status codes.

{
  "jsonrpc": "2.0",
  "id": "mcp-42",
  "error": {
    "code": 200,
    "message": "This move is already posted.",
    "data": { "name": "odoo.exceptions.UserError" }
  }
}

If your MCP server checks only the HTTP status, it will hand the model a "success" that actually failed, and the agent will happily carry on with a wrong assumption. So the server must do the two-step check on every response — verify the status first, then, even on a 200, look for a populated error before returning result to the model. Surfacing that error text back through the tool result is what lets the agent recover ("the invoice was already posted, so I'll skip it"). The complete two-layer pattern and a reusable handler are in Odoo API error handling.

Where this fits — and where to go next

An Odoo MCP server is an integration pattern, not a product feature: a thin, well-described tool layer that lets any MCP-aware agent operate your ERP through a proxy that owns the credentials and bounds the blast radius. Treat the product as early — expose the read paths first, gate the write paths (create, write, unlink, call_method) behind whatever confirmation your agent runtime supports, and grow the tool set as you trust it.