Skip to main content
Glama
Pirainuthal

odoo-mcp-server

by Pirainuthal

odoo-mcp-server

Let Claude (or any MCP client) talk to your Odoo database, read-only by default and guarded by a safety policy.

License: MIT Python 3.10+ Odoo 17 | 18 | 19 MCP SDK 2.x Code style: ruff

odoo-mcp-server is a Model Context Protocol server that exposes an Odoo database to AI assistants as a small set of well-documented tools. You can ask "Which US companies haven't ordered this quarter?" and the assistant finds the models, reads their fields, builds the domain and pages through the results without any custom integration code.

It connects with a login + API key over Odoo's external API. Every call runs as that user, so Odoo's own access rights and record rules still apply. On top of those, the server adds its own safety layer, which is the main point of this project.


Features

  • Discovery tools: list_models finds models and describe_model returns fields, types, required/readonly flags, relations, selection values, and what the user may do.

  • Query tools: search_records (domain, fields, limit, offset, order, paging hints), read_record, count_records.

  • Opt-in write tools: create_record, update_record, delete_record. They are only registered when enabled, so in read-only mode the assistant never sees them.

  • Model-friendly output: many2one becomes {"id", "name"}, empty values become null instead of Odoo's False, and binary fields are replaced by a size marker so they don't fill the context window.

  • Model-friendly input: a many2one accepts 7 or {"id": 7}, an x2many accepts a plain list of ids (or Odoo commands), null clears a field, and a domain can be a list or a JSON string.

  • Actionable errors: "Field 'emial' does not exist on 'res.partner'. Did you mean: email?" Odoo tracebacks are reduced to a single readable line.

  • Resources and prompts: odoo://server-info, odoo://models/{model}, plus the sales_summary and explore_model prompt templates.

  • Operational extras: a --check connection test, stdio and streamable-HTTP transports, request timeouts, thread-safe client, and a fields_get cache.

Related MCP server: Odoo MCP Server

The safety model

Layer

Default

How to change

Read-only mode

Only the five read tools exist

ODOO_MCP_ALLOW_WRITE=1 adds create/update

Deletes

Disabled even when writes are on, including indirect deletes through x2many commands (see below)

ODOO_MCP_ALLOW_DELETE=1 (requires write)

Built-in denylist

API keys, ir.config_parameter, ir.rule, ir.model.access, ir.model.fields, ir.actions.server, base.automation, ir.cron, ir.ui.view, ir.asset, ir.module.*, mail servers, payment providers, ...

Cannot be disabled

Your denylist

none

ODOO_MCP_DENY_MODELS=hr.*,account.* (glob patterns)

Allowlist

all models (except denied)

ODOO_MCP_MODELS=res.partner,sale.*

Result size

50 per page, hard cap 200

ODOO_MCP_DEFAULT_LIMIT, ODOO_MCP_MAX_LIMIT

Batch size

max 100 ids per update/delete

n/a

Relation guard

Dotted domain paths (partner_id.country_id.code) and any sub-domains are checked hop by hop and may not pass through a blocked model; x2many create/update commands may not write into a blocked model

n/a

Argument validation

domains (shape, operators, field names on every hop, prefix-notation arity), field lists, order clauses (injection-proof), ids, write values (types, selections, read-only computed fields, x2many command codes)

n/a

Odoo permissions

Every call runs as the API-key user

Use a dedicated, least-privilege Odoo user

Updates and deletes first check that all ids exist and are accessible. If any are missing, nothing is changed.

x2many writes and deletes. In Odoo, removing lines from a one2many on an existing record (commands 3, 5, 6, or a plain list of ids, which becomes 6) deletes those lines when the inverse field is ondelete="cascade", as it is for order lines. Command 2 always deletes. Because of that, these commands need ODOO_MCP_ALLOW_DELETE=1 as well; in write-only mode the assistant can still add lines ([[0, 0, {...}]]) or link them ([[4, id]]), and many2many sets ([1, 2]) work as usual.

Tip: create a dedicated Odoo user for the assistant (e.g. assistant@yourcompany.com) with only the groups it needs, and generate the API key for that user. The server's policy and Odoo's ACLs then work together.

Architecture

flowchart LR
    A["MCP client<br/>(Claude Desktop, Claude Code, ...)"] -- "stdio / HTTP<br/>JSON-RPC" --> B
    subgraph B["odoo-mcp-server"]
        direction TB
        T["server.py<br/>tools · resources · prompts"] --> S["service.py<br/>OdooService"]
        S --> P["safety.py<br/>SafetyPolicy"]
        S --> Z["serialize.py<br/>JSON-safe in/out"]
        S --> C["client.py<br/>OdooClient (XML-RPC)"]
    end
    C -- "/xmlrpc/2/common<br/>/xmlrpc/2/object<br/>login + API key" --> O[("Odoo 17/18/19<br/>ACLs + record rules")]

A typical search_records call:

sequenceDiagram
    participant M as AI model
    participant S as odoo-mcp-server
    participant O as Odoo
    M->>S: search_records(model, domain, fields, limit)
    S->>S: model allowed? (built-in denylist, deny/allow lists)
    S->>O: fields_get (cached per model)
    S->>S: validate domain, fields, order; check relation paths; clamp limit
    S->>O: search_read + search_count (as the API-key user)
    O-->>S: rows (Odoo ACLs and record rules applied)
    S-->>M: {total, records, next_offset} (many2one → {id, name}, False → null)

Module

Responsibility

config.py

Parses and validates environment variables into an immutable Settings

client.py

Thread-safe XML-RPC client: lazy auth, timeouts, fault → readable error

safety.py

Model policy, write gates, limits, domain/field/order validation

serialize.py

Odoo wire format ↔ JSON-friendly values

service.py

SDK-independent operations (unit-tested without MCP)

server.py

MCP tools/resources/prompts and the CLI

Quick start

1. Install

Requires Python 3.10+.

# Recommended: an isolated install with pipx
pipx install git+https://github.com/Pirainuthal/odoo-mcp-server.git

# or with uv
uv tool install git+https://github.com/Pirainuthal/odoo-mcp-server.git

# or from a clone
git clone https://github.com/Pirainuthal/odoo-mcp-server.git
cd odoo-mcp-server
python -m venv .venv && . .venv/bin/activate
pip install -e .

2. Create an Odoo API key

In Odoo: avatar (top-right) → My Profile / Preferences → Account Security → New API Key. Name it, copy the key, and keep it secret. On Odoo 19 the rpc module must be installed; it is auto-installed on standard databases.

3. Check the connection

export ODOO_URL=https://mycompany.odoo.com
export ODOO_DB=mycompany
export ODOO_USERNAME=assistant@mycompany.com
export ODOO_API_KEY=xxxxxxxxxxxxxxxx

odoo-mcp-server --check
{
  "url": "https://mycompany.odoo.com",
  "database": "mycompany",
  "username": "assistant@mycompany.com",
  "uid": 2,
  "server_version": "19.0",
  "mode": "read-only",
  "allowed_models": "all (except denied)",
  "default_limit": 50,
  "max_limit": 200,
  "always_blocked_models": ["res.users.apikeys", "ir.config_parameter", "..."]
}

4. Connect your AI assistant

Claude Code

claude mcp add odoo \
  -e ODOO_URL=https://mycompany.odoo.com \
  -e ODOO_DB=mycompany \
  -e ODOO_USERNAME=assistant@mycompany.com \
  -e ODOO_API_KEY=xxxxxxxxxxxxxxxx \
  -- odoo-mcp-server

Add --scope user to make it available in every project, then run /mcp inside Claude Code to confirm that the odoo server is connected.

Claude Desktop: edit claude_desktop_config.json (Settings → Developer → Edit Config):

{
  "mcpServers": {
    "odoo": {
      "command": "odoo-mcp-server",
      "env": {
        "ODOO_URL": "https://mycompany.odoo.com",
        "ODOO_DB": "mycompany",
        "ODOO_USERNAME": "assistant@mycompany.com",
        "ODOO_API_KEY": "xxxxxxxxxxxxxxxx",
        "ODOO_MCP_MODELS": "res.partner,sale.*,product.*,account.move"
      }
    }
  }
}

If odoo-mcp-server isn't on the PATH Claude Desktop sees, use the absolute path (for example /home/you/.local/bin/odoo-mcp-server). More examples are in examples/.

Any other MCP client: run odoo-mcp-server as a stdio server. For HTTP clients:

odoo-mcp-server --transport streamable-http --host 127.0.0.1 --port 8000
# MCP endpoint: http://127.0.0.1:8000/mcp

The HTTP transport has no authentication of its own: anyone who can reach it acts as the configured Odoo user. Keep it on localhost or put it behind an authenticating proxy.

Configuration

Variable

Required

Default

Description

ODOO_URL

yes

Base URL, e.g. https://mycompany.odoo.com

ODOO_DB

yes

Database name

ODOO_USERNAME

yes

Login of the API-key user

ODOO_API_KEY

yes

API key (a password also works, but isn't recommended)

ODOO_MCP_ALLOW_WRITE

0

1 registers create_record and update_record

ODOO_MCP_ALLOW_DELETE

0

1 registers delete_record (needs ALLOW_WRITE=1)

ODOO_MCP_MODELS

(all)

Comma-separated allowlist; globs allowed (sale.*)

ODOO_MCP_DENY_MODELS

(none)

Extra denylist on top of the built-in one

ODOO_MCP_DEFAULT_LIMIT

50

Page size when the model doesn't pass limit

ODOO_MCP_MAX_LIMIT

200

Hard cap on limit

ODOO_TIMEOUT

30

XML-RPC timeout in seconds

A template is in .env.example. The server reads the process environment; it doesn't load .env files itself.

Tools, resources and prompts

Tool

Mode

Purpose

list_models(query?, include_transient?, limit?)

read

Find models by technical name or label

describe_model(model, field_query?, include_technical?)

read

Fields (required first), relations, selections, user's access rights

search_records(model, domain?, fields?, limit?, offset?, order?)

read

Paged search; returns total, records, next_offset

read_record(model, record_id, fields?)

read

One record (archived ones included)

count_records(model, domain?)

read

Fast count

create_record(model, values)

write

Returns id and display_name

update_record(model, ids, values)

write

All-or-nothing existence check, max 100 ids

delete_record(model, ids)

delete

Returns the deleted records' names

When fields is omitted, search_records returns a compact projection (display_name plus common fields such as name, state, partner_id, amount_total, and required fields). Pass ["*"] to get every non-binary field.

Resource / prompt

Description

odoo://server-info

Version, database, user, mode and limits (never the key)

odoo://models/{model}

The describe_model output as a resource

sales_summary(period)

Guided, read-only sales report for a period

explore_model(model)

Guided tour of a model: purpose, key fields, sample rows

Example conversations

The traces below were produced against an Odoo 19 database with demo data.

"Which US companies do we have, and what are their emails?"

→ search_records(model="res.partner",
                 domain=[["is_company","=",true],["country_id.code","=","US"]],
                 fields=["name","country_id","email"], limit=3, order="name")
← {"total": 7, "returned": 3, "next_offset": 3,
   "records": [
     {"id": 9,  "name": "Acme Corporation", "country_id": {"id": 233, "name": "United States"},
      "email": "acme_corp@yourcompany.example.com"},
     {"id": 15, "name": "Azure Interior",   "country_id": {"id": 233, "name": "United States"}, ...},
     {"id": 16, "name": "Lumber Inc", ...}]}

Claude: There are 7 US companies. The first three alphabetically are Acme Corporation,
Azure Interior and Lumber Inc. Want me to fetch the remaining four?

"How many contacts have a child contact with an example.com email?"

→ count_records(model="res.partner",
                domain=[["child_ids","any",[["email","ilike","example"]]]])
← {"model": "res.partner", "count": 8}

Typos are caught before Odoo sees them

→ search_records(model="res.partner", fields=["emial"])
← error: Field 'emial' does not exist on 'res.partner'. Did you mean: email?

Sensitive models are off-limits regardless of who asks

→ search_records(model="res.users.apikeys")
← error: Model 'res.users.apikeys' is security-sensitive and always blocked by this server.

Odoo's permissions still apply (here as a non-admin user, with writes enabled)

→ create_record(model="res.country", values={"name": "Atlantis", "code": "XA"})
← error: You are not allowed to create 'Country' (res.country) records.
         This operation is allowed for the following groups: - Role / Administrator

Read-only by default: with the default configuration, tools/list returns only the five read tools, so a request like "rename Acme to Acme Inc" has no tool to act on. The server's instructions tell the assistant that write tools appear only when they're enabled.

Testing

python -m venv .venv && . .venv/bin/activate
pip install -e ".[dev]"

pytest              # unit tests, no Odoo needed
ruff check src tests && ruff format --check src tests

The unit tests use an in-memory fake Odoo (tests/conftest.py) that implements the XML-RPC common/object services (domain evaluation, fields_get, search_read, create, write, unlink, access checks and faults). They cover configuration, every safety rule, serialization in both directions, fault translation, and the MCP layer end to end through an in-process MCP client (tool listing, schemas, annotations, error results, prompts and resources).

Live smoke test (optional, against a real server; use a test database):

export ODOO_URL=http://localhost:8069 ODOO_DB=test ODOO_USERNAME=admin ODOO_API_KEY=...
ODOO_MCP_LIVE_TEST=1 pytest tests/test_live.py                     # read-only checks
ODOO_MCP_LIVE_TEST=1 ODOO_MCP_LIVE_WRITE=1 \
  ODOO_MCP_ALLOW_WRITE=1 ODOO_MCP_ALLOW_DELETE=1 pytest tests/test_live.py   # + create/update/delete a temp partner

Compatibility notes

  • Odoo 17, 18, 19 are supported. describe_model uses has_access (18+) and falls back to check_access_rights on 17.

  • Odoo 19 marks /xmlrpc/2 as deprecated, with removal planned for Odoo 22, and logs a warning per call. You can mute it with --log-handler odoo.addons.rpc.controllers.xmlrpc:ERROR. A JSON-2 transport is on the roadmap.

  • Built on MCP Python SDK 2.x (MCPServer, the successor of FastMCP).

Roadmap

  • JSON-2 API transport (/json/2/<model>/<method>, bearer API key) for Odoo 19+

  • read_group / aggregation tool for reports without fetching rows

  • Per-model field denylist (e.g. hide hr.employee private fields)

  • Optional confirmation step for writes through MCP elicitation

  • Audit log of write operations

  • Publish to PyPI so uvx odoo-mcp-server works

Contributing

Issues and pull requests are welcome. Please run ruff format, ruff check and pytest before opening a PR; CI runs the same checks on Python 3.10–3.13.

License

MIT © 2026 Pirainuthal S

Available Tools

5 tools
count_recordsCount Odoo recordsA
Read-only

Count the records matching a domain (cheap; no data is transferred).

NameRequiredDescriptionDefault
modelYesTechnical model name, e.g. 'res.partner', 'sale.order', 'account.move'.
domainNoOdoo domain: a list of [field, operator, value] conditions, optionally combined with '&', '|', '!' in prefix notation. Conditions are AND-ed by default. Example: [["customer_rank", ">", 0], ["country_id.code", "=", "IN"]]. Omit or pass [] to match all records.

Output Schema

NameRequiredDescription

No output parameters

TDQS

A3.6/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true and openWorldHint=true, so the safety profile is covered. The description adds a genuine behavioral trait beyond them — that the call is cheap and transfers no record data — but says nothing about permissions, errors on invalid model names, or how an omitted domain affects results.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single front-loaded sentence with zero waste; the key verb and resource come first and the efficiency note is a compact qualifier. Nothing could be removed without losing information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists and the parameters are fully documented, so return values and input format needn't be explained. The remaining gap is minor: no note on what happens with an unknown model or which models are accessible, but an agent has what it needs to call it correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, with both 'model' and 'domain' fully documented including operator syntax and the empty-domain behavior. The description mentions the domain concept but adds no syntax or format detail beyond the schema, so the baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description gives a specific verb ('Count') and resource ('records matching a domain'), which is clearly distinguishable from search_records or read_record by the counting semantics. It does not explicitly name a sibling as the alternative, so it stops short of a 5.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The parenthetical '(cheap; no data is transferred)' implies you should reach for this when you only need a total rather than the records themselves, but the description never says 'use this instead of search_records when...'. Usage must be inferred from the efficiency hint rather than stated.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

describe_modelDescribe an Odoo modelA
Read-only

Describe a model's fields (type, label, required, readonly, relation, selection values) and what the current user is allowed to do with it.

Required fields are listed first.

NameRequiredDescriptionDefault
modelYesTechnical model name, e.g. 'res.partner', 'sale.order', 'account.move'.
field_queryNoOnly show fields whose name or label contains this text.
include_technicalNoInclude chatter/activity/internal fields (message_*, activity_*).

Output Schema

NameRequiredDescription

No output parameters

TDQS

A4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true and openWorldHint=true, so safety is covered. The description adds valuable context beyond annotations: it discloses which field attributes are returned (type, label, required, readonly, relation, selection values), that permissions for the current user are included, and that required fields are ordered first. It does not describe response size, performance, or error behavior, but the output schema covers return shape.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two short sentences with no wasted language. The key purpose and returned information are front-loaded, and the ordering detail is placed efficiently after the main description.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given a rich input schema with 100% description coverage, explicit read-only/open-world annotations, and a present output schema, the description is nearly complete for an agent to call it correctly. The main gap is guidance on when to choose this tool over siblings, though that is more of a usage concern.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so all three parameters are already well documented in the input schema. The description does not add parameter-specific semantics such as how field_query filters fields or when include_technical should be used, so baseline 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description uses a specific verb ('Describe') and resource ('a model's fields') and enumerates the returned field attributes and permission information. It clearly distinguishes schema introspection from sibling tools like list_models (which lists models) and read_record (which fetches records).

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies usage for inspecting a model before querying or reading records, but it does not explicitly state when to use this tool versus list_models, search_records, or read_record. No exclusions or alternative-routing guidance is provided.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_modelsList Odoo modelsA
Read-only

List the Odoo models this server can access, optionally filtered by name.

Models blocked by the safety policy are never listed.

NameRequiredDescriptionDefault
limitNoMaximum models to return.
queryNoCase-insensitive filter on technical name or label, e.g. 'sale'.
include_transientNoInclude transient (wizard) models.

Output Schema

NameRequiredDescription

No output parameters

TDQS

A3.8/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint and openWorldHint, so safety of the call is known. The description adds non-obvious context: models blocked by the safety policy are never listed, which explains why a model the agent expects may be absent. Pagination/limit behavior is left to the schema.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two short sentences, zero waste. The core purpose is front-loaded and the scope caveat follows immediately.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With annotations covering read-only semantics and an output schema present, the description only needs to establish scope and any surprising behavior — which it does via the access scope and safety-policy exclusion. Only minor gap is that limit/pagination behavior is not mentioned.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so each parameter is already fully documented, including that the query filters on technical name or label. The description only restates that filtering is by name, adding nothing beyond the schema. Baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource: 'List the Odoo models this server can access.' The scoping phrase 'this server can access' plus the safety-policy note makes the domain clear. It does not explicitly contrast with siblings like describe_model, but the resource (models vs records) already separates it.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage is only implied ('optionally filtered by name'). There is no statement of when to prefer this over describe_model or search_records, and no prerequisites or exclusions beyond the safety-policy note.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

read_recordRead one Odoo recordB
Read-only

Read a single record by id (all non-binary fields unless fields is given).

NameRequiredDescriptionDefault
modelYesTechnical model name, e.g. 'res.partner', 'sale.order', 'account.move'.
fieldsNoField names to return. Omit for a compact default set; pass ['*'] for all non-binary fields. Use describe_model to discover field names.
record_idYesThe record id.

Output Schema

NameRequiredDescription

No output parameters

TDQS

B3.2/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint and openWorldHint, so safety is covered. The description's one behavioral claim — that omitting `fields` returns "all non-binary fields" — conflicts with the schema, which says omitting returns a compact default set and ['*'] is needed for all non-binary fields. That misleading default is a real defect for an agent deciding what to pass.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

One short sentence with the core operation front-loaded and no filler. It is arguably too terse given the tool's scope, but nothing in it is wasted.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return values need no explanation, and the read-only profile is covered by annotations. Still, the field-default discrepancy leaves an agent unsure what a bare call actually returns, which is the one thing this description should have settled.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the schema already explains model, record_id and the fields wildcard/default behavior in detail. The description only restates the `fields` behavior, and restates it inaccurately, adding no correct meaning beyond the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ("Read a single record by id") and the id-based lookup implicitly separates it from search_records. It never names a sibling tool, so the differentiation is inferred rather than stated.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

"by id" implies you must already know the record id, which is a usable usage cue, but there is no explicit when-to-use versus search_records/count_records. The only routing hint ("Use describe_model...") lives in the schema, not the description.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

search_recordsSearch Odoo recordsA
Read-only

Search records matching a domain and return selected fields.

Returns the total number of matches, the current page of records and, when more records exist, next_offset for the following page.

NameRequiredDescriptionDefault
limitNoPage size (default 50, max 200).
modelYesTechnical model name, e.g. 'res.partner', 'sale.order', 'account.move'.
orderNoSort clause, e.g. 'date_order desc, id'.
domainNoOdoo domain: a list of [field, operator, value] conditions, optionally combined with '&', '|', '!' in prefix notation. Conditions are AND-ed by default. Example: [["customer_rank", ">", 0], ["country_id.code", "=", "IN"]]. Omit or pass [] to match all records.
fieldsNoField names to return. Omit for a compact default set; pass ['*'] for all non-binary fields. Use describe_model to discover field names.
offsetNoRecords to skip, for paging.

Output Schema

NameRequiredDescription

No output parameters

TDQS

A3.7/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true and openWorldHint=true, so safety is covered, and the description goes further by disclosing the pagination contract: total match count, current page, and next_offset only when more records exist. That is genuinely useful behavioral context beyond the annotations, though it says nothing about cost, result caps beyond the schema's limit, or access restrictions.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two short sentences with the core action front-loaded and the return contract following. No filler, though the sentence break mid-thought and the absent sibling guidance mean it is efficient rather than exemplary.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists and the input schema is fully documented, so the description does not need to restate parameters or return shape, and it correctly focuses on the paging behavior. What is missing is the disambiguation from read_record and count_records, which is the one thing the structured fields cannot supply.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100% and the domain, fields, order, limit, and offset parameters are all documented in detail in the schema, so the schema carries the load. The description adds no parameter-level meaning (no default limit mention, no domain syntax note), so the baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

Names a specific verb (search) and resource (records) plus the two things it does: filter by a domain and project a field set. It is clear on its own, but nothing in the text distinguishes it from read_record or count_records, so an agent must infer the boundary from the names alone.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage is only implied: the mention of a domain and paging suggests bulk/filtered retrieval, but there is no explicit 'use this instead of count_records when you need the rows' or 'use read_record for a single known ID'. With three closely related siblings, the guidance an agent actually needs is left to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 5 tool updatesv0.1.0
    • First observedcount_records
    • First observeddescribe_model
    • First observedlist_models
    • First observedread_record
    • First observedsearch_records

TDQS

A3.9/5.0

Scored across 5 tools

Disambiguation5/5

Each tool has a clearly distinct role: model discovery, schema description, multi-record search, single-record read, and cheap counting. Although count_records overlaps slightly with search_records' total-match behavior, the explicit cost distinction makes the boundary clear.

Naming Consistency5/5

All tool names follow the same snake_case verb_noun convention: list_models, describe_model, search_records, read_record, count_records. Pluralization is also used predictably for collection vs. single-record operations.

Tool Count5/5

Five tools is a compact, well-scoped set for read-only Odoo data access. Each tool earns its place: discovery, schema introspection, search, single-record read, and count are all non-redundant primitives.

Completeness4/5

The read-only lifecycle is well covered: model listing, field descriptions, paginated search, single-record read, and counting. Write operations are absent, but the safety-policy wording suggests this is intentional; minor gaps include no binary/attachment read or aggregate/group-by support.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    D
    maintenance
    Enables AI assistants to interact with Odoo data using natural language to search, read, create, and update records. It acts as a secure bridge between MCP clients and Odoo instances version 17.0 through 19.0.
    11
    Apache 2.0
  • A
    license
    Not graded
    quality
    D
    maintenance
    Provides read-only access to Odoo databases via OdooRPC, allowing AI assistants to query and analyze Odoo data through the MCP protocol.
    1
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    A read-only MCP server that enables AI assistants to query Odoo instances via XML-RPC, supporting search, read, count, and field inspection without requiring custom modules.
    68 PyPI
    1
    Mozilla Public 2.0
  • A
    license
    Not graded
    quality
    D
    maintenance
    An MCP server that enables AI assistants to safely query live Odoo 19 data with a defense-in-depth security layer, providing read-only tools for partners, sales, inventory, and finance.
    MIT