Skip to main content
Glama
My Awesome MCP Server

My Awesome MCP Server

by fabiancasta

odoo-mcp-governed

An MCP server for Odoo where every tool call is policy-checked, risk-scored, and written to a tamper-evident audit log before it is allowed to touch the ERP.

Giving an AI agent write access to the system that holds your invoices, customers and stock is a straightforward thing to build and a hard thing to justify. The usual MCP server hands the model a set of tools and hopes. This one assumes the agent will eventually do something wrong — because its instructions changed, because it was prompt-injected, because its token leaked — and is built so that when that happens the damage is bounded and the evidence survives.

Status: v0.3 — all 19 tools, stdio and HTTP transports, admin panel and Docker deployment work end to end against Odoo 19. 569 tests, 15 of them clicking through the panel in a real browser.

What the operator gets

Panic switch

One click refuses every agent call on the next request. The panel and the audit log keep running, and it survives restarts until you resume.

Block one agent

Refuse every call from one token; the other agents keep working.

Undo with one click

Every create, write and delete is journalled with its old and new values. Undo writes the old values back, deletes what was created, or recreates what was deleted.

Human approval

Deletes, bulk changes or execute_method can wait for a person. The agent gets a request number; you approve in the panel (and get a webhook or Telegram message); the agent repeats the exact call once.

Live dashboard

Calls per hour, blocked attempts by kind, most used models, agent risk meters — refreshed every ten seconds.

Threshold console

Faders for throttle and block scores, delete bursts, call rate and risk memory, applied live and audited.

Permissions by app

Odoo's own models grouped by app with read/write/delete ticks. Models installed later show as "new — not reviewed" and stay blocked until you tick them.

Sandbox profiles

For test databases: model lists off and risk only observed, while the denylist, credential fields and audit stay on. Never on production.

Activity report

A printable "what your AI did" page per client and period — counts, refusals, changes — with no record values, ready to send as a PDF.

Several clients

Pin each agent's token to its client's Odoo profile; switching the active profile can never send it to another client's database.

Connect from Claude.ai or ChatGPT

OAuth 2.1 with PKCE (S256) and dynamic registration. The app sends you to the panel; you choose which agent it becomes and on which profile. It gets an ordinary registered token — revocable from the agent's tree. Redirects must be HTTPS (HTTP only to localhost); codes are hashed, single use, ten minutes.

MCP 2026-07-28

Stateless requests with server/discover; headers that must match the body (MCP-Protocol-Version, Mcp-Method, Mcp-Name); self-describing results. Clients from 2024-11-05 to 2025-11-25 keep working.

Content guards

Text addressed to an AI is caught in English, Spanish, Portuguese and French, through look-alike letters, full-width forms and invisible characters. Writing such text, or changing anything in the 30 minutes after reading some, waits for a person. At most 10 requests per agent wait at once.

No account takeover

The e-mail of a contact that belongs to a user — the address a password reset goes to — is never changed through MCP.

Nothing leaks sideways

Links to models the agent may not read (user_id → res.users…) come back empty; chatter, activities and followers follow the governance of the record they belong to; Odoo's errors reach the agent without the list of groups that would grant access.

Strict calls

Unknown arguments (sudo, uid…) are refused, not ignored; a method's context accepts only lang, tz, active_test and allowed_company_ids; execute_method with no allowlist refuses every method.

Idempotent creates

idempotency_key on create_record and upload_attachment: a retry returns the first result instead of a duplicate.

Two layers, in plain words

The Permissions page first shows what Odoo itself lets the profile's user do — asked of Odoo, per app — then three brakes on every agent: may change, may delete, a person approves deletes and bulk changes. Passwords, keys, certificates, modules, code, users and settings stay out whatever the switches say. Per-app and per-model lists are under Advanced.

Permissions per profile

One server, several Odoo (Spain, Colombia…): each profile can have its own lists, or follow the default ones. Each app gets one level in one click — none, read, change, delete — with a selector per model to fine-tune, a search box, and Odoo's system models kept apart.

Archive or delete agents

Revokes every token, blocks the agent and hides it; restore any time. The audit log keeps its history.

Clickable dashboard

Tabs (summary, Odoo profiles, policy). Every number, bar, refusal kind and model opens the audit log filtered to exactly those events.

Agent tree

Each agent — with a rule or only seen in the audit log — opens as a tree: profiles → what it can really read, change and delete there, model and tool limits, record scope, tokens, activity; with + to add.

Agent registry

Every token is listed and can be revoked on its own, or all of one agent's at once — no JWT_SECRET rotation. A rule per agent narrows the policy: fewer models or tools, read-only, a daily call quota, one pinned profile.

Record scope

A domain per model, e.g. a WhatsApp bot sees only res.partner id = {partner} and that customer's sale.order. {partner} comes from the token's signed variables, so one rule serves one token per customer. Searches are ANDed with the scope, ids outside it are refused, new records get the scope's values.

Injection warning

Record content is data. Text in it that addresses an AI ("ignore previous instructions…") still comes back, plus a separate warning to the agent and an audit entry.

Less approval fatigue

Approve all similar pending requests at once, or a kind of call (agent + tool + model) for 30 min, 2 h or 8 h, up to 50 uses — never for modules or code. A burst of similar requests notifies once.

Two-factor panel

ADMIN_TOTP_SECRET adds an authenticator-app code to the panel login; a code works once.

Related MCP server: OdooSurface MCP

How a call is handled

Tools cannot reach Odoo except through the governance engine. Every call passes three stages, in this order:

ToolCall
   │
   ├─ 1. Policy ──────── permitted at all?        → deny, and record the denial
   │                     (allowlists, per-operation
   │                      gates, volume ceiling)
   │
   ├─ 2. Detection ───── what does this look like  → signals, with weights
   │                     in context?               (8 behavioural detectors)
   │
   ├─ 3. Risk ────────── signals + this actor's     → allow / flag / throttle / deny
   │                     decayed recent history
   │
   └─ 4. Audit ───────── hash-chained record of what was asked,
                         what was decided, and what happened

Risk can narrow what policy permits. It can never widen it. A policy denial is final regardless of how low the risk score is, and no threshold setting can make a forbidden call allowed. If that relationship were bidirectional, tuning a number would be a way to grant permissions, and the allowlist would stop being the authority on what is allowed.

1. Policy — the preventive layer

Default deny. An empty ALLOWED_MODELS permits nothing, so every widening is something a human typed.

Read, write and delete are separate gates. Being allowed to read res.partner does not imply writing it; being allowed to write does not imply deleting. Most agent integrations need to read broadly and write narrowly, and a single "allowed models" list cannot express that.

execute_method — arbitrary method on arbitrary model, the widest surface in any Odoo API wrapper — is off by default, takes a method allowlist, and refuses the ORM write primitives outright. Calling unlink through it would otherwise delete records while never touching the delete gate.

Credential fields are matched by pattern, so the ones modules add under compound names (stripe_secret_key, unsplash_access_key, signup_token) are caught too, including on models installed after the server was configured. They are refused on every model, always. SENSITIVE_FIELDS adds patterns; it cannot remove the built-in floor (*password*, *secret*, *token*, *api_key*, *apikey*, *private_key*).

The denylist wins over any allowlist, * included. Besides the models that run code or hold credentials directly, it covers the indirect routes: res.config.settings, which reads and writes ir.config_parameter on your behalf, and module install and uninstall. Attachment content is only served by get_attachment, which checks the record the file belongs to; the generic read tools refuse it and leave it out. Reads with no field list also skip binary fields, so a contact does not arrive with ten copies of its photo.

2. Detection — the behavioural layer

Eight detectors, each looking for something a legitimate integration does once and almost never does repeatedly:

Detector

What it catches

allowlist_probing

Repeated denied calls, especially spread across models — the shape of someone mapping what is reachable. Weighted highest: it has no benign high-volume explanation.

delete_burst

Several deletes in quick succession. A loop that lost its exit condition, or an injected instruction being carried out.

mass_read

Large cumulative read volume. Each call is under the per-call cap and perfectly legal; only the sum reveals exfiltration.

write_amplification

One call writing or deleting a batch far above routine, but below the hard cap.

rate_anomaly

Volume spikes, both absolute and relative to this actor's own baseline. The relative check is what catches a quiet actor suddenly getting busy.

repeated_errors

A run of failures — an agent guessing at schema it does not have.

privilege_drift

An established actor touching a model it never has before.

off_hours

Activity outside the configured window. Weighted low on purpose.

Detectors describe; they never decide. Each emits weighted signals and the engine applies the thresholds. A heuristic that can refuse a call on its own is a heuristic that can take down production by itself — and relatedly, a detector that crashes is dropped with a note in the log rather than failing the call, because a bug in statistical code should not be an outage.

Each detector's docstring states how it produces false positives. A detector whose failure mode you cannot describe is one you will end up ignoring in practice. The defaults are tuned to start noisy and be tightened against real traffic, rather than start silent and hope.

3. Risk — turning signals into a decision

The score combines the current call's signals with the actor's recent ones, discounted by an exponential half-life (15 minutes by default). Patterns worth catching are built from individually unremarkable calls — the third delete is only alarming because of the first two — so history has to count. It has to decay just as much, or an actor that tripped a detector once is permanently degraded and the operator switches governance off.

Two kinds of signal are counted differently. A delete burst or an oversized write is new evidence each time, so those add up: three deletes in a minute score 25, the fourth 50, the fifth 75 and is refused. Off-hours, a high call rate, an unfamiliar model or a run of errors describe a state that every call in it reports again; those count once, at their strongest decayed weight. Summing them was how an ordinary assistant session used to get blocked within minutes. Calls refused because of risk do not feed the score that refused them, so an agent that keeps retrying still cools off, and the score is capped at 100.

Scoring is per-actor: one misbehaving agent does not raise another's risk.

Score

Decision

below throttle

allow, or allow_flagged if anything fired

≥ RISK_THROTTLE_THRESHOLD

throttle — the call succeeds after a real delay, turning a runaway loop into a trickle so a human has time to notice

≥ RISK_BLOCK_THRESHOLD

deny

4. Audit — the forensic layer

An append-only SQLite log where each event stores the hash of the one before it, so every hash depends on the entire history.

What the log deliberately does not contain: argument values. Odoo records hold personal and commercial data, and an audit log is the wrong place to accumulate a second copy of it. Stored instead are field names (useful, not sensitive) and a SHA-256 digest of the arguments, which lets you prove two calls were identical without being able to read either.

Each event does record the ids a call created, changed or deleted, as part of the hashed chain — "which record?" is the first question after an incident.

Denials are recorded, not dropped. A refusal is evidence, and the probing detector is built entirely out of refusals.

The values live somewhere else on purpose: the change journal (data/changes.db, 0600, CHANGE_JOURNAL) keeps the old and new values of every create, write and delete, linked to the audit event that sealed it, with credential fields stripped. The panel shows it under Changes. Keeping it separate means the audit log can be handed to an investigator without handing over the ERP.

Threat model, stated plainly

This is where most "tamper-proof log" claims quietly overreach.

  • Detected. Editing, deleting, reordering or splicing in a past event. The chain breaks at a named sequence number. Repairing the hash of the row you edited does not help — the next event still commits to the old one, so the break just moves forward one.

  • Detected only with AUDIT_HMAC_KEY set. An attacker who can read and write the database file and knows the record format can, with a plain SHA-256 chain, recompute every hash from the altered point forward and produce a chain that verifies. An HMAC chain closes this off for anyone who has the file but not the key.

  • Not detected by this module alone. An attacker who owns the running process has the key and can rewrite everything, head included. No single-host design fixes that. The answer is anchoring: periodically copy the head hash somewhere the server cannot reach back into. AuditLog.anchor exists for this and it is a two-line cron job, not a product.

  • Also not detected. Anything that bypasses this server and talks to Odoo directly. Governance here covers the MCP surface; claiming more would be dishonest.

Known limitation. The audit event is written once, after the outcome is known. If the process dies mid-call, the attempt leaves no trace. Recording intent before and outcome after would close that gap at the cost of doubling the log; for now the single event wins, and startup self-check verifies the log is writable so the common failure is caught before it matters.

Running it

Claude Desktop (stdio)

{
  "mcpServers": {
    "odoo": {
      "command": "odoo-mcp-governed",
      "args": ["stdio"],
      "env": {
        "ODOO_URL": "https://erp.example.com",
        "ODOO_DB": "mydb",
        "ODOO_USERNAME": "bot@example.com",
        "ODOO_API_KEY": "…",
        "ODOO_ENVIRONMENT": "staging",
        "ALLOWED_MODELS": "res.partner,sale.order",
        "AUDIT_HMAC_KEY": "…",
        "DATA_DIR": "/home/me/.local/share/odoo-mcp-governed"
      }
    }
  }
}

HTTP, with the admin panel

cp .env.example .env && chmod 600 .env    # fill in JWT_SECRET, ADMIN_PASSWORD, AUDIT_HMAC_KEY
mkdir -m 700 -p data
docker compose up -d                       # listens on 127.0.0.1:8020
odoo-mcp-governed token --actor claude     # one token per agent

The MCP endpoint is POST /mcp (POST / also works). The admin panel at /admin manages Odoo profiles (switching is instant, no restart), edits the policy live — tools on/off, model lists, execute_method — shows the token, and has an audit view with filters and a per-actor risk table. It contains no JavaScript at all and is served under a CSP that forbids scripts.

Administrative actions — logins, profile switches, policy changes — go into the same hash chain as agent calls. They are recorded but never risk-scored: an administrator switching a tool off at 03:00 during an incident must not be locked out by off-hours signals.

Command line

odoo-mcp-governed audit verify   # exit 1 if the chain is broken — cron it
odoo-mcp-governed audit anchor   # append the head hash to data/anchors.log
odoo-mcp-governed token --actor wabot --var partner=42 --hours 2   # registered, scoped
odoo-mcp-governed revoke --actor wabot   # refuse every token of one agent, no restart
odoo-mcp-governed totp-setup     # writes data/totp-setup.txt (0600): secret + otpauth:// URI

Production safeguards

Pointing a development setup at the live ERP is the most ordinary way to lose data, so there are two independent guards:

  • Unlabelled means production, and production is read-only. Every profile carries an environment. A profile without one — typically copied in from another tool — is treated as production, where creating, writing, deleting and execute_method are refused at the policy layer. Writing to a test database requires labelling it development or staging; writing to production requires ticking allow writes on that one profile, which the panel then shows in red.

  • PROTECTED_HOSTS lists hosts the server must never contact. A profile pointing at one (or any subdomain) cannot be saved or activated, and if the active profile turns out to point at one, the server starts with no profile rather than connect.

  • The policy is not the only barrier. A production profile whose Odoo user has Settings access — or whose access cannot be verified — refuses * model lists: if a policy rule were ever missing, nothing in Odoo would stop the agent. server_status warns about such a user on any profile, and reports writes_enabled with a plain-language note instead of raw flags.

Access levels

Each profile has one level:

Level

For

Reaches

Never

business (default)

Agents that work with the business: sales, collections, support, analysis

Business models in the lists; * means every business model

System models (ir.*, users, groups, settings, modules, payment providers…) unless named exactly

sandbox

Test data and controlled activities

Business models without lists; risk observed only

System models; the denylist

configurator

A consulting agent setting Odoo up for a client

The whole system: modules, settings, users, groups, views, automations

API keys, ir.attachment through the generic tools, credential fields, secret-looking system parameter values. Installing or removing modules and adding code wait for a human

Sandbox and configurator are refused on production profiles. Files are listed per record with list_attachments and fetched with get_attachment, both checked against the record they belong to; keys and certificates are never handed out, whatever they belong to.

A least-privilege production profile

The MCP policy and Odoo's own access control should each be enough on their own. For production:

Layer

Setting

Odoo user

A dedicated user (for example mcp_agent) with an API key, only the groups of the apps the agent works with, and neither Settings nor Technical

Models

Explicit ALLOWED_MODELS and WRITABLE_MODELS; DELETABLE_MODELS empty unless needed

Methods

ALLOW_EXECUTE_METHOD off, or a closed EXECUTE_METHOD_ALLOWLIST such as action_confirm,message_post (an empty one refuses every method)

Denylist

Kept as the floor, not relied on as the main control

Secrets

A fresh JWT_SECRET (rotating it revokes every token) and AUDIT_HMAC_KEY

Tokens

One per agent from the Agents page, then REQUIRE_REGISTERED_TOKENS=true

Several Odoo

Pin every agent to its profile, then REQUIRE_PINNED_PROFILE=true: an unpinned agent is refused instead of following the active profile

Panel

ADMIN_TOTP_SECRET set, HTTPS in front

Hardening carried over from day one

/docs and the OpenAPI schema are not served. Bodies are capped by counting bytes, so chunked uploads are capped too. 500s carry no detail. Security headers on every response, HSTS in HTTPS mode. Mcp-Session-Id is validated, never reflected. /mcp is rate-limited per actor, with a per-IP ceiling before authentication and a separate limit on failed authentications; batches are capped and every message in one counts. A foreign Origin header is refused (DNS rebinding). /health and login are limited per client IP, with X-Forwarded-For honoured only from configured proxies. Credentials and the audit database are written 0600 from the first byte, atomically. TLS to Odoo is verified by default. The container runs as a non-root UID on a read-only root filesystem with all capabilities dropped, no-new-privileges, memory/CPU/PID limits and its own network, publishing on 127.0.0.1 only. For publishing beyond localhost, deploy/firewall-docker.sh restricts the port through the DOCKER-USER chain, since Docker bypasses UFW, and deploy/egress-docker.sh stops the container from opening connections to anything but Odoo — not the internet, not the LAN, not a database on the host.

Full mapping, item by item: docs/SECURITY_BASELINE.md.

Red-teaming it

Unit tests prove each rule; redteam/ attacks a running server.

  • redteam/harness.py — fixed, repeatable attacks with an expected outcome each: the denylist and its indirect routes, credential fields (domains, dotted paths, ordering, nested specifications), attachment content, wizards and blocked methods, transport limits, probing, the panic switch, agent blocking, a delete burst, and the audit chain afterwards. Standard library only.

  • redteam/canaries.py — plants random secrets in a disposable Odoo where no agent may read them.

  • redteam/agent.py — an adversarial LLM, through any OpenAI-compatible API (Groq's free tier by default), told to steal the canaries, mass-delete and talk its way past the server. A canary in anything it received is a leak and fails the run; its opinion of how it did is not the verdict.

  • .github/workflows/redteam.yml — runs it on GitHub's machines against a throwaway Odoo 19 built by redteam/ci_world.sh: the harness on every push and pull request, the LLM attacker by hand with two model families. Actions are pinned by commit SHA and images by digest, with Dependabot proposing bumps.

The harness found a denial of service (a flood of bad tokens locked out valid agents on the same IP) and the canaries found a content leak (ir.attachment.index_content) on their first runs; both are fixed.

Tests

The suite has 569 tests (pytest tests/e2e drives the panel in Chromium with Playwright). The ones that matter are not the "it records events" kind — they are the adversarial ones:

python -m pytest tests/ -q
  • Tamper-evidence (test_audit_chain.py) reaches past the public API into the SQLite file to simulate an attacker with direct database access, because going through the public API would prove nothing — it has no way to modify a past event. Modification, deletion, hash repair, forged insertion and the unkeyed-vs-HMAC distinction are each asserted to be caught, at the right sequence number.

  • No permission leakage (test_policy.py) asserts read does not imply write, write does not imply delete, and that unlink and write cannot be laundered through execute_method even when explicitly allowlisted.

  • Signal, not noise (test_detectors.py) every detector is tested for silence on ordinary traffic as well as for firing on its pattern. One test runs a realistic workload through all eight and asserts zero signals — if that fails, every alert the system raises is noise.

  • The core invariant (test_engine.py) risk cannot widen policy; a crashing detector does not fail the call or inflate the score; actors are scored independently; a delete burst that starts permitted ends refused.

  • Concurrency is not a bypass (test_concurrency.py) ten deletes fired in parallel are judged as ten, not as ten first deletes each seeing an empty history. The test was checked by removing the per-actor lock: without it, all ten get through.

  • Never reaches Odoo (test_tools.py) tools run against a fake Odoo that records every request, so refusals are asserted as "Odoo saw nothing", not merely "an error came back".

  • Production stays read-only (test_production_guard.py) and protected hosts cannot be activated, whatever the profile file says.

  • HTTP hardening (test_http.py) alg: none, expired, wrong-issuer and expiry-less tokens; CSRF; chunked oversized bodies; secrets never in HTML; markup injected through audit data rendered inert.

The key security tests were mutation-checked — CSRF, the chunked body limit, secrets kept out of HTML, the domain password oracle and the per-actor lock: the protection was removed by hand and the test confirmed to fail, so that a passing test means something.

Tests drive a fake clock rather than real time, since every detector is a statement about when things happened.

Configuration

See .env.example. There are no insecure defaults: the server refuses to start in HTTP mode without ADMIN_PASSWORD and JWT_SECRET, TLS verification toward Odoo is on, and the model allowlist is empty.

Development

python -m venv .venv && . .venv/bin/activate
pip install -e ".[http,dev]"
pytest -q
ruff check src tests

A disposable Odoo 19 CE instance for testing (port 65010):

cd test-env && docker compose up -d
docker compose exec -T web odoo -d govtest -i base \
  --stop-after-init --db_host=db --db_user=odoo --db_password=odoo

Roadmap

  • Governance core — policy, detectors, risk scoring, hash-chained audit

  • Odoo client (JSON-RPC, real timeouts) and multi-profile hot swap

  • The 19 MCP tools, all routed through the engine

  • stdio transport (Claude Desktop) and HTTP transport (JWT)

  • Admin panel: profiles, live policy, tokens, audit log with risk view

  • Production safeguards: read-only by default, protected hosts

  • Hardened Docker image and compose file

  • Change journal with before and after values

  • One-click undo from the change journal

  • Panic switch and per-agent block

  • Human approval for destructive or bulk operations (panel, webhook, Telegram)

  • Live dashboard, threshold console, permissions by app, sandbox profiles

  • Activity report per client; tokens pinned to a profile

  • Per-profile model lists, with profile tabs in the panel

  • Approve from the phone (Telegram buttons / WhatsApp reply)

  • Two-phase audit (intent before the call, outcome after)

  • Agent registry: per-token revocation, per-agent rules and daily quotas

  • Record-level scope per agent, filled from signed token variables

  • Prompt-injection warning, grouped and standing approvals, TOTP for the panel

  • Signed off-box anchoring (today: audit anchor writes a file to ship)

Provenance

Written from scratch and MIT licensed. No code was copied from any existing Odoo MCP server. The implementation follows the published Odoo external API and the Model Context Protocol specification; the feature set and the security baseline come from the author's own experience running and hardening an internal Odoo MCP deployment, documented in docs/SECURITY_BASELINE.md.

License

MIT — see LICENSE.

Available Tools

18 tools
cache_statsC
Read-only

Metadata cache statistics.

NameRequiredDescriptionDefault

No parameters

TDQS

C2.7/5.0
Behavior2/5

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

Annotations already declare readOnlyHint=true and destructiveHint=false, so the safety profile is covered by structured data. The description adds nothing beyond that: it does not say whether the call is cheap, whether it reflects live or cached state, or what kind of counts are reported. With annotations in place the bar is lower, but no added behavioral context is present.

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

Conciseness3/5

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

It is a single four-word fragment with no waste, but it is under-specified rather than genuinely concise. There is no front-loaded verb or scope statement to anchor the reader.

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

Completeness2/5

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

There is no output schema, so the description carries the burden of conveying what the statistics contain. For a diagnostic tool with no annotations describing its return shape, 'statistics' is too thin to tell an agent what it will receive or whether the result is worth requesting.

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

Parameters4/5

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

The tool takes zero parameters, which is the baseline-4 case per the rubric. The description correctly implies no input is needed, and there is no parameter surface to misdescribe.

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

Purpose3/5

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

The fragment 'Metadata cache statistics' names a resource (the metadata cache) and the kind of data returned (statistics), but supplies no verb and does not specify what 'statistics' means. It is distinguishable from write siblings like create_record, but not clearly distinguished from the nearest sibling, server_status.

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

Usage Guidelines2/5

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

There is no statement of when to call this tool, when not to, or how it relates to alternatives such as server_status or model_info. The agent is left to infer that this is a diagnostic/introspection call.

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

call_onchangeA
Read-only

Simulate a form change and return the values Odoo would compute. Saves nothing.

NameRequiredDescriptionDefault
modelYesTechnical model name, e.g. 'res.partner'
valuesYesCurrent form values
field_namesNoFields that changed (default: keys of values)
fields_specNoOptional onchange spec

TDQS

A3.8/5.0
Behavior3/5

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

The annotations already declare readOnlyHint=true and destructiveHint=false, and 'Saves nothing' reinforces that safety profile rather than contradicting it. However, the description adds little beyond the annotations: it does not describe error behavior, how the simulated values are structured, or any server-side effects. With annotations carrying the safety burden, this lands at a solid 3.

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 zero filler; the core behavior is front-loaded and the no-persistence guarantee follows immediately. Every clause earns its place.

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?

For a 4-parameter tool with a nested object and no output schema, the description usefully signals what comes back ('the values Odoo would compute') and that nothing is written. The remaining gap is that the shape of the returned computed values is left unspecified, but the essential call-correctness information is present.

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 all four parameters (including the nested 'values' and 'fields_spec') documented in the schema itself. The description adds no syntax, format, or default information beyond what the schema already states, so the baseline 3 is appropriate.

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 and resource: 'Simulate a form change and return the values Odoo would compute.' An agent can immediately tell this is a dry-run computation, not a persistence operation. It does not explicitly name or contrast a sibling such as update_record, which keeps it from 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 Guidelines4/5

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

'Simulate' plus 'Saves nothing' conveys a clear usage context: call this when you want computed onchange values without mutating data, making it the safe preview counterpart to update_record/create_record. There is no explicit statement of when NOT to use it or a named alternative, so it stops short of 5.

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

create_recordC

Create a record.

NameRequiredDescriptionDefault
modelYesTechnical model name, e.g. 'res.partner'
valuesYesField values

TDQS

C2.5/5.0
Behavior2/5

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

Annotations declare readOnlyHint=false and destructiveHint=false, which is consistent with 'Create', so there is no contradiction. However, the description adds nothing beyond them: no mention of required permissions, validation/constraint failures, side effects (e.g. computed fields, mail/activity triggers), or that it mutates the database.

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

Conciseness3/5

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

One sentence with zero waste, but that brevity comes at the cost of under-specification rather than deliberate concision. Nothing is padded, but nothing is front-loaded that an agent can act on either.

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

Completeness2/5

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

For a required two-parameter mutation with a nested free-form 'values' object and no output schema, the agent still lacks essentials: how field values should be encoded (scalar vs relation commands), which model names are valid, and what is returned on success. The description does not fill any of these gaps.

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% (both 'model' and 'values' are documented in the schema), so the baseline is 3. The description provides no additional meaning, and notably says nothing about the shape of the free-form 'values' object beyond the schema's 'Field values'.

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

Purpose3/5

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

The sentence does state a specific verb (Create) and resource (record), but it is effectively a restatement of the tool name create_record and adds no distinguishing detail over siblings like update_record or get_record. It is not misleading, but an agent learns nothing beyond the name.

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

Usage Guidelines2/5

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

There is no guidance on when to use this instead of update_record, get_record, or execute_method, and no prerequisites or context of use are stated. The agent must infer the routing entirely from the tool name.

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

delete_recordB
Destructive

Delete one or more records. Irreversible.

NameRequiredDescriptionDefault
idsNoSeveral record ids
modelYesTechnical model name, e.g. 'res.partner'
record_idNoOne record id

TDQS

B3.4/5.0
Behavior3/5

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

Annotations already declare destructiveHint=true and readOnlyHint=false, so the dangerous nature is known structurally. The description's 'Irreversible' adds a small amount of context (permanence, no undo), but says nothing about permissions, cascading effects on related records, or partial-failure behavior for batch deletes.

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 filler, with the mutating action stated first and the irreversibility caveat immediately after. Nothing is buried or redundant.

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?

For a three-parameter destructive tool with no output schema and full schema coverage, the description covers the essentials but leaves gaps: access requirements, the single-vs-batch id exclusivity rule, and the consequences of deleting a model with linked records are all unaddressed.

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

Parameters4/5

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

Schema coverage is 100%, so the baseline is 3, but the description's 'one or more records' actively explains the relationship between the two mutually exclusive inputs (record_id vs ids) in a way the schema alone does not spell out.

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 ('Delete ... records') and clarifies cardinality ('one or more'), so the agent can distinguish it from create_record/update_record in the sibling list. It does not name the alternatives explicitly, but the operation is unambiguous.

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

Usage Guidelines2/5

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

The description gives no when-to-use or when-not-to-use guidance and never references alternatives such as update_record or the read tools. 'Irreversible' is a warning rather than usage routing, so the agent must infer context on its own.

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

execute_methodB
Destructive

Call a public model method, e.g. sale.order.action_confirm. Restricted by policy.

NameRequiredDescriptionDefault
argsNoPositional args; record ids first
modelYesTechnical model name, e.g. 'res.partner'
kwargsNo
methodYes

TDQS

B3.1/5.0
Behavior3/5

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

Annotations already declare readOnlyHint=false and destructiveHint=true, so the safety profile is covered structurally. The description adds one piece of context beyond that — execution is 'restricted by policy' — but never explains what the policy allows, whether the call is reversible, or the risk of invoking arbitrary model methods. Moderate added value against an already-informative annotation set.

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 clauses, front-loaded with the action and example, with no filler. It is efficient, though the extreme brevity borders on under-specification for a tool that executes arbitrary server-side methods.

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

Completeness2/5

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

For a destructive, policy-gated, arbitrary-method executor with four parameters and no output schema, the description is too thin. It omits what the call returns, which methods are permitted, and how the positional args/kwargs map to the target method — information an agent needs to invoke 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 coverage is 50%: 'args' and 'model' are described in the schema, while 'method' and 'kwargs' are not. The description's example clarifies the expected dotted method format, partially compensating, but 'kwargs' remains undocumented in both places.

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 (call) and resource (a public model method) with a concrete example ('sale.order.action_confirm'), which tells an agent this is a generic method-invocation escape hatch. It does not explicitly contrast itself with the specific CRUD siblings (create_record, update_record, delete_record), so sibling differentiation is only implied.

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

Usage Guidelines2/5

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

'Restricted by policy' hints at a gate but never states when to prefer this over the specific sibling tools (create_record, update_record, call_onchange, etc.) or when it is forbidden. No when-to-use, when-not-to-use, or alternative routing is provided.

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

get_attachmentB
Read-only

Download an attachment as base64.

NameRequiredDescriptionDefault
attachment_idYes

TDQS

B3.2/5.0
Behavior3/5

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

Annotations already declare readOnlyHint=true and destructiveHint=false, so the safety profile is covered. The description adds the one behavior the annotations don't: the payload is returned base64-encoded. It says nothing about size limits, failure modes, or whether the attachment must belong to a record the caller can access.

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 front-loaded sentence with no waste. It is arguably too terse for a tool with zero parameter documentation, but nothing in it is redundant.

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?

For a simple read tool with no output schema, the base64 disclosure is the key piece of information and it is present. Missing are anything about response size, binary handling, or error behavior when the id is invalid or inaccessible.

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 0% and the single parameter is an undescribed integer. The description implies the id selects 'an attachment' but gives no format, range, or provenance hints (e.g., where attachment_id comes from). With only one obvious parameter, this is a minor but real gap.

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 ('Download') and resource ('attachment'), and adds the return encoding ('as base64'), which cleanly separates it from the sibling upload_attachment. It stops short of naming that sibling explicitly, so full differentiation requires reading the sibling list.

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

Usage Guidelines2/5

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

There is no when-to-use guidance, no prerequisites, and no reference to alternatives such as upload_attachment or get_record. The agent must infer the context entirely from the name.

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

get_field_namesB
Read-only

Compact list of a model's field names with type and label.

NameRequiredDescriptionDefault
modelYesTechnical model name, e.g. 'res.partner'

TDQS

B3.2/5.0
Behavior3/5

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

Annotations already declare readOnlyHint=true and destructiveHint=false, so the safety profile is covered. The description adds the notable trait that the payload is 'compact' (field names plus type and label only), implying a lightweight response, but says nothing about pagination, ordering, or behavior for unknown models.

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, front-loaded sentence fragment with zero filler; the returned attributes are stated immediately. It is efficient but so terse that it borders on under-specification rather than model conciseness.

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?

For a one-parameter read tool this covers the essentials, and the description does hint at return content (name, type, label) despite there being no output schema. However, it leaves the sibling-selection question (get_model_fields vs model_info) and edge-case behavior unexplained.

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% and the single `model` parameter is already documented with an example ('res.partner'). The description adds nothing about the parameter, 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?

States a clear verb-equivalent ('list') and a specific resource ('a model's field names'), plus the returned attributes (type and label). It does not differentiate from the sibling get_model_fields or model_info, which sound like overlapping read paths, so the agent can't tell from the description alone which to pick.

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

Usage Guidelines2/5

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

No when-to-use statement, no prerequisites, and no mention of alternatives such as get_model_fields, model_info, or list_models. The agent must infer that this is the lightweight metadata lookup rather than the fuller field-definition tool.

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

get_model_fieldsB
Read-only

Field definitions of a model (type, relation, required...).

NameRequiredDescriptionDefault
modelYesTechnical model name, e.g. 'res.partner'
attributesNo

TDQS

B3/5.0
Behavior3/5

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

Annotations already declare readOnlyHint=true and destructiveHint=false, so the safety profile is covered. The description adds a hint about the shape of the returned data (type, relation, required), which is useful since there is no output schema, but it omits filtering/pagination behavior.

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?

A single compact sentence with the core resource front-loaded and no waste. It is arguably too terse for a two-parameter tool, but nothing is padded.

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?

For a simple read tool with no output schema and annotations covering safety, the description is minimally viable, but it leaves the role of 'attributes' and the actual return format unexplained, and gives no routing against closely related siblings like get_field_names.

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

Parameters2/5

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

Schema coverage is only 50%: 'model' is documented with an example ('res.partner'), but the 'attributes' array parameter has no description in either the schema or the tool description, so the agent cannot tell whether it filters, expands, or restricts the fields returned. With low coverage the description should compensate and does not.

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 resource ('field definitions of a model') and enumerates what the definitions contain ('type, relation, required...'). It implicitly distinguishes itself from the sibling get_field_names by promising richer metadata, though it never names that sibling explicitly.

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

Usage Guidelines2/5

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

There is no guidance on when to call this versus get_field_names, model_info, or list_models, all of which sound adjacent. No prerequisites or exclusions are given.

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

get_recordA
Read-only

Read one or more records by id.

NameRequiredDescriptionDefault
idsNoSeveral record ids
modelYesTechnical model name, e.g. 'res.partner'
fieldsNoFields to return. Omit for all readable fields — prefer naming them.
record_idNoOne record id

TDQS

A3.5/5.0
Behavior3/5

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

The annotations already declare readOnlyHint=true and destructiveHint=false, so the safety profile is covered. The description adds 'one or more' (clarifying batch behavior) but says nothing about behavior for missing ids, empty results, or record not found — with annotations carrying the main burden, this is adequate but thin.

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?

A single front-loaded sentence with no wasted words — the core action and lookup key come first. It is arguably too terse for the batch semantics, but nothing is redundant or padded.

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?

For a simple read tool with full schema coverage and annotations covering safety, the description is nearly sufficient. The one gap is that no output schema exists and the description doesn't characterize the return value, though 'read records by id' makes the expected shape fairly predictable.

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 ids, model, fields, and record_id are all documented in the schema itself, including the note that omitting 'fields' returns all readable fields. The description adds no parameter meaning beyond that, 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 pairs a specific verb ('Read') with a specific resource ('records') and adds the scoping phrase 'by id', which distinguishes it from the sibling search_records. It stops short of naming that sibling explicitly, so a small amount of differentiation is left to inference.

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?

There is no explicit when-to-use statement, but the phrase 'by id' implicitly signals this tool is for direct lookups when identifiers are already known, as opposed to search_records or name_search. No exclusions or prerequisites are stated.

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

get_reportB
Read-only

Render a PDF report (e.g. an invoice) for records. Returns base64.

NameRequiredDescriptionDefault
idsNoSeveral record ids
record_idNoOne record id
report_nameYese.g. 'account.report_invoice'

TDQS

B3.3/5.0
Behavior3/5

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

Annotations already declare readOnlyHint=true / destructiveHint=false, and the description is consistent with that. It adds one genuinely useful trait beyond the annotations – the base64 return encoding – but says nothing about report availability, size limits, or failure behavior for an invalid report_name.

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 filler, with the action front-loaded and the return format trailing. Nothing to cut.

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?

For a single-required-parameter render tool with no output schema, the description covers the essentials and even states the return encoding. However it does not resolve whether 'ids' and 'record_id' are alternatives or combinable, or whether omitting both is legal, leaving a real gap for correct invocation.

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 the three parameters are already documented in the schema, establishing the baseline of 3. The description adds no clarification of the relationship between the two optional id selectors ('ids' vs 'record_id'), which is the one semantic point the schema leaves ambiguous.

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 ('Render a PDF report ... for records') with a concrete example ('e.g. an invoice'), which clearly separates it from the record CRUD and attachment siblings. It stops short of explicitly naming what it is not, but the action is unambiguous.

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

Usage Guidelines2/5

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

No when-to-use guidance, no prerequisites, and no mention of alternatives. An agent must infer that this is the only reporting tool and cannot tell from the text whether it is a read-only render or has side effects on stored records.

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

list_modelsB
Read-only

List the models this server allows access to, optionally filtered.

NameRequiredDescriptionDefault
limitNo
filterNoSubstring of the model name

TDQS

B3/5.0
Behavior3/5

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

Annotations already declare readOnlyHint=true and destructiveHint=false, so the safety profile is covered. The description adds a modest amount of context by noting the list is scoped to models 'this server allows access to', hinting at permission-based visibility, but says nothing about pagination, ordering, or default limit behavior.

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?

A single tight sentence with the scope constraint front-loaded. No wasted words, though it is arguably too terse for a tool with an undocumented parameter.

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?

For a simple read-only list tool with no output schema and only two optional params, the description is minimally adequate. The undocumented 'limit' parameter and absent mention of result ordering or pagination leave small but real gaps.

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

Parameters2/5

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

Schema coverage is only 50% – the 'limit' parameter has no description in either the schema or the description. The description's 'optionally filtered' weakly gestures at the filter param without explaining matching semantics beyond what the schema already says ('Substring of the model name'), so it does not compensate for the coverage gap.

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 (List) and resource (models) with a scope qualifier ('this server allows access to'). However, it does not distinguish itself from siblings like model_info, get_model_fields, or name_search, which an agent could plausibly confuse with a model-listing tool.

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

Usage Guidelines2/5

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

The phrase 'optionally filtered' implies the filter parameter is available but gives no guidance on when to use this tool versus model_info, get_model_fields, or search_count. No alternatives, prerequisites, or exclusions are named.

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

model_infoC
Read-only

Model description, Odoo access rights and what this server's policy permits.

NameRequiredDescriptionDefault
modelYesTechnical model name, e.g. 'res.partner'

TDQS

C2.8/5.0
Behavior3/5

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

Annotations already declare readOnlyHint=true and destructiveHint=false, so the safety profile is covered structurally. The description adds genuinely useful scope beyond that: it discloses that the response includes Odoo access rights and this server's own policy decisions, which is non-obvious for a tool named model_info. It still omits error behavior for unknown models and any auth requirements, so it is useful but not rich.

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

Conciseness3/5

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

One short sentence fragment with zero padding and the most distinctive payload (access rights, policy) front-loaded after the generic 'model description'. However, the verbless fragment form makes it under-specified rather than crisply concise.

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?

With one required parameter, no output schema, and read-only annotations, the tool is simple, and the description does gesture at the three things returned. But without an output schema the description should more concretely characterize those return values (shape, whether policy differences cause errors), and it says nothing about behavior for invalid or inaccessible models.

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 single 'model' parameter is documented with an example ('res.partner'), so the schema carries the load. The description adds nothing about the parameter, which is acceptable at this coverage but earns only the baseline.

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

Purpose3/5

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

The description is a noun phrase listing the payload ('model description, Odoo access rights and what this server's policy permits') rather than a verb+resource statement, so the agent must infer that this is a metadata/inspection read. It does name the resource (a model) and hint at non-obvious return content, but it gives no differentiation from siblings like get_model_fields, get_field_names, or list_models, which all sound like overlapping metadata lookups.

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

Usage Guidelines2/5

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

There is no guidance on when to call this versus get_model_fields, get_field_names, or list_models, and no stated preconditions (e.g. whether the model must exist or the user must have access). The agent is left to guess from the sibling names alone.

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

search_countC
Read-only

Count records matching a domain.

NameRequiredDescriptionDefault
modelYesTechnical model name, e.g. 'res.partner'
domainNoOdoo search domain, e.g. [["customer_rank", ">", 0]]

TDQS

C2.9/5.0
Behavior2/5

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

Annotations already declare readOnlyHint=true and destructiveHint=false, so the safety profile is covered. The description adds nothing beyond that: it doesn't say what happens when 'domain' is omitted (implied: count all records of the model), nor whether the count honors record rules/access rights. For a count tool with annotation coverage, this is thin.

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?

A single front-loaded sentence with the verb first and zero filler. It is efficient, though its brevity comes at the cost of the guidance noted in other dimensions.

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?

For a simple read-only counting tool with fully documented parameters and no output schema, the minimum needed to call it correctly is present. Missing is the key behavioral note that omitting 'domain' counts every record in the model, plus any routing hint toward search_records when full rows are needed.

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 schema already documents both 'model' and 'domain' with concrete examples, so the schema carries the parameter burden. The description only echoes 'domain' and adds no format or edge-case detail beyond it; 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 ('Count') and resource ('records matching a domain'), which lets an agent distinguish it from the sibling search_records that returns records rather than a tally. However, it never names or contrasts with that sibling explicitly, so differentiation is implied 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 Guidelines2/5

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

There is no guidance on when to prefer this over search_records, nor any mention of prerequisites or exclusions. The use case (avoid fetching full records when only a total is needed) is only inferable from the tool name.

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

search_recordsC
Read-only

Search records with an Odoo domain and return the matching rows.

NameRequiredDescriptionDefault
limitNoMax rows (default 100)
modelYesTechnical model name, e.g. 'res.partner'
orderNoe.g. 'create_date desc'
domainNoOdoo search domain, e.g. [["customer_rank", ">", 0]]
fieldsNoFields to return. Omit for all readable fields — prefer naming them.
offsetNo

TDQS

C2.9/5.0
Behavior2/5

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

Annotations already declare readOnlyHint=true and destructiveHint=false, and the description only echoes that read semantics via 'return the matching rows.' It adds nothing about pagination, the default 100-row cap, or behavior when limit is omitted, which are the operationally relevant traits for a search tool.

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 sentence, zero filler, with the action and the return stated up front. It is efficient rather than padded, though its brevity borders on under-specification for a six-parameter tool.

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

Completeness2/5

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

For a six-parameter search with one required field and no output schema, the description leaves major gaps: pagination via limit/offset, default row cap, field-selection guidance, and behavior on an invalid domain are all unaddressed. Annotations cover only the safety profile, so the description should carry far more of this burden.

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 83%, so the schema already documents model, domain, fields, order, and limit syntax. The description only restates 'Odoo domain' and adds no format or default guidance 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?

Specific verb+resource (search records) with the mechanism named (Odoo domain) and the return subject stated (matching rows). It is clear what the tool does, but it never differentiates itself from siblings like search_count or name_search, which sound nearly identical to an outside reader.

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

Usage Guidelines2/5

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

No when-to-use, when-not-to-use, or alternative is given. An agent must infer that search_count is for totals and name_search is for name lookups; the description provides no routing signal at all.

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

server_statusB
Read-only

Server, active Odoo profile and governance status.

NameRequiredDescriptionDefault

No parameters

TDQS

B3.1/5.0
Behavior3/5

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

Annotations already declare readOnlyHint=true and destructiveHint=false, so the safety profile is covered. The description adds that the payload covers server, profile and governance state, which is meaningful context beyond the annotations, but it says nothing about freshness, cost, or whether the call is cheap or expensive.

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?

A single short noun phrase with zero padding, so it is maximally concise. It is slightly under-specified rather than bloated, which is a mild structural weakness rather than a verbosity problem.

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?

With no output schema, the description is the only place that can tell the agent what comes back, and it only lists three high-level areas without any shape or detail. For a no-parameter diagnostic tool this is adequate but leaves the return content largely opaque.

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

Parameters4/5

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

The tool takes zero parameters, so there is nothing for the description to disambiguate; baseline 4 applies. No parameter-related gap exists.

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

Purpose3/5

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

The description names three resources it reports on (server, active Odoo profile, governance) but omits a verb, so it reads as a label rather than an action. It does implicitly distinguish itself from the CRUD-oriented siblings, which all target records rather than diagnostic state.

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

Usage Guidelines2/5

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

No indication of when to call this versus alternatives, no prerequisites, no mention of whether it should be used for health checks or troubleshooting. The agent must infer all usage context from the name alone.

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

update_recordC

Update one or more existing records.

NameRequiredDescriptionDefault
idsNoSeveral record ids
modelYesTechnical model name, e.g. 'res.partner'
valuesYesField values to write
record_idNoOne record id

TDQS

C2.9/5.0
Behavior2/5

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

Annotations already declare readOnlyHint=false and destructiveHint=false, so the safety profile is covered. Beyond that the description adds nothing: it does not say what happens to fields omitted from 'values' (merge vs overwrite), whether a partial or id-less failure aborts the whole batch, what permissions are needed, or what the update returns.

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?

A single front-loaded sentence with zero filler; the verb and scope arrive immediately. Its weakness is under-specification rather than bloat, and nothing would need to be cut to improve it.

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?

For a four-parameter mutation tool with full schema coverage, no output schema, and annotations that already cover the write/safety profile, the definition is minimally viable. It still omits batch-failure semantics and the partial-update behavior of 'values', which genuinely matter for a bulk write.

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 of the four parameters is documented in the schema and the baseline is 3. The description's 'one or more' faintly signals the ids-vs-record_id duality, but it adds no syntax, model naming, or values-format detail beyond what the schema already provides.

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 ('update') and resource ('existing records') plus the multiplicity ('one or more'), so the agent knows this is a bulk-capable mutation rather than a read or create. It never contrasts itself with create_record or delete_record, so it lacks explicit sibling differentiation, but the verb alone is unambiguous.

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

Usage Guidelines2/5

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

No guidance on when to use this tool versus create_record, delete_record, or execute_method, and no preconditions such as required permissions or model-level write access. The 'one or more' phrasing implies bulk use but never states when the single-record path is preferable.

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

upload_attachmentC

Attach a file (base64) to a record.

NameRequiredDescriptionDefault
modelYesTechnical model name, e.g. 'res.partner'
filenameYes
mimetypeNo
record_idYesOne record id
data_base64Yes

TDQS

C2.9/5.0
Behavior3/5

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

Annotations already declare readOnlyHint=false and destructiveHint=false, so the agent knows this is a non-destructive write. The description adds only the base64 payload format, which is already implied by the data_base64 parameter name; it says nothing about size limits, permissions, whether an existing attachment is replaced, or what happens on success.

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?

A single short sentence with the action and payload format front-loaded and zero filler. It is efficient, though its brevity borders on under-specification rather than being a model of density.

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

Completeness2/5

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

For a mutation tool with no output schema, four required parameters, and only 40% schema coverage, the description leaves too much open: no success/return behavior, no permission or size constraints, and no guidance on the optional mimetype. The annotations cover the safety profile but not the operational details.

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

Parameters2/5

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

Schema description coverage is only 40%, so the schema does not carry the load. The description mentions 'base64' and 'record' but does not clarify mimetype (optional), filename semantics, the technical model name convention, or the record_id target beyond what the parameter names already convey.

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 (attach/upload) and resource (a base64 file) plus the target (a record), which is enough to distinguish it from the read-side sibling get_attachment. It stops short of explicitly naming that sibling or contrasting upload vs download, so it is clear but not maximally differentiated.

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

Usage Guidelines2/5

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

There is no when-to-use or when-not-to-use guidance, no mention of the alternative get_attachment for retrieval, and no prerequisites such as required access to the target model/record. The agent must infer all routing from the tool name alone.

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. 18 tool updatesv0.1.0
    • First observedcache_stats
    • First observedcall_onchange
    • First observedcreate_record
    • First observeddelete_record
    • First observedexecute_method
    • First observedget_attachment
    • First observedget_field_names
    • First observedget_model_fields
    • First observedget_record
    • First observedget_report
    • First observedlist_models
    • First observedmodel_info
    • First observedname_search
    • First observedsearch_count
    • First observedsearch_records
    • First observedserver_status
    • First observedupdate_record
    • First observedupload_attachment

TDQS

B3.3/5.0

Scored across 18 tools

Disambiguation4/5

Most tools target clearly distinct actions (CRUD, search, metadata, attachments, reports). The main overlap is get_model_fields vs get_field_names (full field definitions vs compact list), and there are three search variants (search_records, name_search, search_count), though their descriptions do clarify the boundaries.

Naming Consistency4/5

Strong, predictable verb_noun snake_case pattern (get_record, search_records, create_record, update_record, delete_record, execute_method, etc.). Minor deviation: a few noun-only names (model_info, server_status, cache_stats) break the verb-prefix pattern but remain readable and conventional for status/info tools.

Tool Count4/5

18 tools is on the heavier side but each maps to a real capability of an Odoo ORM surface (CRUD, search, metadata introspection, method execution, reports, attachments). Slightly over-scoped rather than bloated, with no obviously redundant tools.

Completeness5/5

Covers the full lifecycle: create/read/update/delete, multiple search modes, model and field introspection, method calling, onchange simulation, PDF reports, and attachment upload/download, plus status and cache tooling. No obvious dead ends for the domain.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    Not graded
    maintenance
    Enables AI assistants to interact with Odoo ERP systems through natural language to search records, create entries, update data, and manage business operations. Supports secure authentication and configurable access controls for production environments.
    Mozilla Public 2.0
  • A
    license
    A
    quality
    A
    maintenance
    Enables AI agents to interact with Odoo ERP as the authenticated user, with tools for discovery, planning, and mutations bounded by user permissions.
    36
    50 npm
    2
    Apache 2.0
  • F
    license
    Not graded
    quality
    C
    maintenance
    Enables AI assistants to interact with Odoo ERP via the external API. It supports listing models, describing schemas, searching, aggregating, creating, updating, deleting records, and invoking business methods, with a configurable permission matrix for safety.
    -
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables AI assistants like Claude, ChatGPT, and Copilot to connect to Odoo ERP, providing tools to search, read, create, and update any Odoo model such as partners, sales orders, invoices, and products.
    8 npm
    AGPL 3.0