Skip to content
verify mcp Beta VerifyMCP is currently in beta. If you notice any issues, get in touch and we’ll put it right.

io.github.singleflo/odoo-assistant

PYPI · ODOO-ASSISTANT · SCANNED AUG 20

Odoo virtual employee via MCP: query, create, modify records, workflows, PDFs, and activities.

Available components

65 Trust /100
Trust breakdown (6 categories)

How this component scores in each security and reliability category. Every signal is checked automatically from public evidence about the published package, including repeated runs of it in an isolated sandbox, and we only credit what we can confirm. How we score →

Supply Chain Security100
  • No malware found by supply-chain analysis.Pass
  • No known CVEs affecting this package version or its production dependencies.Pass
  • Runs hatchling.build at install time, a recognised native-build step with no shell scripting around it. View diagnostics → Pass
  • 2 of 30 dependencies flagged as unhealthy. View diagnostics → Partial
Provenance & Transparency35
  • Source repository is publicly reachable at the declared URL. View diagnostics → Pass
  • Provenance check failed: no build-provenance attestation is published. See how to fix → View diagnostics → Fail
  • License check failed: the license (MIT License) isn't a recognized OSI-approved license. See how to fix → Fail
  • Actively maintained (last published 1 days ago).Pass
  • Publishes a security disclosure policy (SECURITY.md).Pass
Schema Quality & AI Usability81
  • 100% of prompts and resources have a non-trivial description (not blank, and not just the item's name).Pass
  • AI-judged instruction clarity (good).Pass
  • Context-footprint check failed: tool/resource definitions use about 3683 tokens (~136/item across 27 items; 19 tools + 8 resources), over budget; trim descriptions and params. See how to fix → Fail
  • Usage-examples check failed: none of the tools include examples. See how to fix → Fail
Stability & Change Management0
  • Stability not yet verified: not enough scan history yet (needs a 30-day window).Unverified
Tool Coverage71
  • 100% of tools have a non-trivial description (not blank, and not just the tool's name).Pass
  • 0% of tool parameters carry a description.Fail
  • Structured output schemas are declared (100% of tools); any adoption earns full credit.Pass
Capabilities100
  • Implements a current MCP spec version (2026-07-28).Pass

Unverified: 1 category

A category scored 0 because we could not verify it: a data source with nothing on this package, evidence we could not reach, or a check we could not run. We only credit what we can confirm.

Install

Add this component to your MCP client. Where a client-specific snippet is available, pick your client below and copy it straight into your config; otherwise use the connection detail shown.

pypi · odoo-assistant

# add to Claude Code
claude mcp add singleflo-odoo-assistant -- uvx odoo-assistant
# add to Codex CLI
codex mcp add singleflo-odoo-assistant -- uvx odoo-assistant
// opencode.json
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "singleflo-odoo-assistant": {
      "type": "local",
      "command": [
        "uvx",
        "odoo-assistant"
      ],
      "enabled": true
    }
  }
}
# add to OpenClaw
openclaw mcp add singleflo-odoo-assistant --command uvx --arg odoo-assistant
# ~/.hermes/config.yaml
mcp_servers:
  singleflo-odoo-assistant:
    command: "uvx"
    args: ["odoo-assistant"]
// mcp.json
{
  "mcpServers": {
    "singleflo-odoo-assistant": {
      "command": "uvx",
      "args": [
        "odoo-assistant"
      ]
    }
  }
}
Changelog

Every change we have recorded for this component, newest first. Security-relevant changes are always shown. ▲ marks a change for the better, ▼ a change for the worse; unmarked changes are neutral.

  • 19 Aug 26 +15
    • Malware scan: unverified → pass security
  • 18 Aug 26 50

    First indexed and scored.

Diagnostics

Diagnostic detail from the automated scan of this channel: what the scanner observed at each step, so you can see exactly where a check passed or failed. It is informational only and never changes the trust score.

Captured 20 Aug 2026 · Analysed pypi/odoo-assistant@0.1.1

Provenance No attestation

The registry publishes no build provenance for this version, so there is nothing to verify.

Result No attestation
Ecosystem pypi
Install scripts 1 script
Hook Tier Command
build_backend allowlisted hatchling.build
Dependencies 30 packages
Packages resolved 30
Stale 1
No linked repository 1
Tree resolution Complete
MCP tools · 19 exposed · ~3,546 tokens

The tools this component advertises to a client, with an estimated token cost for each. Expand a tool to see its parameters and schema. The per-tool counts are indicative and are not scored directly; the schema's total context footprint is one signal in Schema Quality & AI Usability.

Tool Tokens
cancel_record ~52

Cancel a record through `action_cancel`, following the wizard it returns. Destructive, so the default ceiling refuses it and says what would not.

NameTypeReqDescription
modelstringyes
record_idintegeryes
NameTypeReqDescription
resultstringyes

No examples provided.

count_records ~214

Count the records matching a domain (Odoo `search_count`). A count is only as honest as its domain: * `account.move` / `account.move.line` without a `move_type` filter is refused — it would count invoices, bills, credit notes and journal entries together and match no figure the user has ever seen. * A count answers "how many", never "how much". For an amount, read `amount_total_signed` (company currency) and never `amount_total`. * On a multi-company instance the count differs per company: pass `company_ids` or you are reporting one company as the whole business. Args: model: Odoo model, e.g. "crm.lead". domain: Odoo domain. Omit to count everything the model holds. company_ids: Companies to count in, e.g. [1, 2].

NameTypeReqDescription
company_ids
domain
modelstringyes
NameTypeReqDescription
resultstringyes

No examples provided.

create_activity ~197

Schedule an activity: the only notification that carries a deadline. A chatter note is passive. An activity appears in the assignee's To-Do list and turns overdue when the date passes. Args: model: the Odoo model, e.g. "crm.lead". record_id: id of the record the activity hangs off. summary: the one-line title the assignee will read. user_id: res.users id of the assignee. days: deadline offset from today, in days. activity_type: substring of an activity type name, e.g. "call". Activity types differ per instance; the first available type is used when this is omitted or matches nothing.

NameTypeReqDescription
activity_type
daysinteger
modelstringyes
record_idintegeryes
summarystringyes
user_idintegeryes
NameTypeReqDescription
resultstringyes

No examples provided.

create_record ~162

Create a record, reusing an existing match when `unique_on` is given. `unique_on` is a list of FIELD NAMES taken from `values` (e.g. `["name", "email"]`): they are searched first and the existing id comes back instead of a duplicate. Odoo has no idempotency key, so a create that is retried is simply a second record — this is the only protection there is, and a cold-start run without it produced four identical customers. Multi-company: put `company_id` in `values`. The context decides what is visible, not which company owns the new record.

NameTypeReqDescription
modelstringyes
unique_on
valuesobjectyes
NameTypeReqDescription
resultstringyes

No examples provided.

download_docs ~163

Save every document of a record to disk — chatter files included. Returns {"saved": [paths], "skipped": [[name, why]]}. `skipped` is not noise: a database restored without its filestore keeps the attachment rows and loses the bytes, and an empty result would read exactly like "this record has no attachments". Args: model: the Odoo model, e.g. "account.move". record_id: id of the record whose documents to fetch. dest_dir: directory to write the files into. Defaults to this platform's temporary directory — "/tmp" does not exist on Windows.

NameTypeReqDescription
dest_dirstring
modelstringyes
record_idintegeryes
NameTypeReqDescription
resultstringyes

No examples provided.

explore_module ~152

Discover a module's structure by interrogating the live instance. Args: module_name: Module to explore, e.g. "helpdesk". Must be a module slug, since it names the reference file on "generate"; ignored on "list". action: "generate" (the default) writes the reference document, "list" ranks what is worth exploring. models: Comma-separated models for a module the script does not know, e.g. "superchat.message,superchat.template". Defaults to the script's own grouping for `module_name`.

NameTypeReqDescription
actionstring
modelsstring
module_namestringyes
NameTypeReqDescription
resultstringyes

No examples provided.

generate_pdf ~158

Render the PDF of a record and return where it was saved. An already rendered PDF is reused. Otherwise the model's own print/send wizard produces it, and that wizard can also SEND the document — which is why this is gated on `action_send_and_print` (L3_STATE_CHANGE) rather than as a plain read. Args: model: the Odoo model, e.g. "account.move". record_id: id of the record to print. dest_dir: directory to write the PDF into. Defaults to this platform's temporary directory — "/tmp" does not exist on Windows.

NameTypeReqDescription
dest_dirstring
modelstringyes
record_idintegeryes
NameTypeReqDescription
resultstringyes

No examples provided.

instance_overview ~244

Summarise the connected instance: version, companies, volumes per area, in-house modules, anomalies. The profile is built from the instance this server is CONNECTED to, and cached per instance — `census.profile_path()` keys it on the live client, not on an environment variable. That distinction is the whole point: with two instances profiled on one machine, choosing by `ODOO_DB` (which is discovered now, so often unset) once fell through to "the first file on disk" and reported a neighbour's numbers as this instance's, with no error and a perfectly plausible report. First call against a new instance builds the profile, which costs a second or so; every later call is free. Pass `refresh=True` after the instance has changed — the report carries the timestamp it was taken. When drilling into these figures, the two rules that keep them meaningful: filter `account.move` by `move_type`, and sum `amount_total_signed`, never `amount_total`. Args: refresh: rebuild the profile from the instance instead of reusing it.

NameTypeReqDescription
refreshboolean
NameTypeReqDescription
resultstringyes

No examples provided.

list_known_modules ~24

List the modules this server has learned: name, generation date, records.

Input schema present but exposes no named parameters.

NameTypeReqDescription
resultstringyes

No examples provided.

list_message_targets ~242

Who can be messaged and where — ASK THIS BEFORE SENDING ANYTHING. Two lists in one call, because an agent that cannot see the roster invents ids: * `users`: the internal, active users, each with `im_status` — 'online', 'away' (idle 30 minutes) or 'offline'. Presence is worth reading first: a Discuss message is delivered either way, but "offline" tells you nobody is going to answer right now. * `conversations`: the ones the sender already belongs to and has not archived, with `channel_type` — 'chat' is a 1-to-1, 'group' is a private multi-party, 'channel' is a room that may hold the whole company. `members` and `unread` are there so a broadcast is a deliberate choice rather than a surprise. Use `send_direct_message` for a person and `send_channel_message` for a conversation in this list. Neither of them is the tool for annotating an invoice or an order — that is `notify_user`.

Input schema present but exposes no named parameters.

NameTypeReqDescription
resultstringyes

No examples provided.

notify_user ~445

Write a note on a record's chatter and notify the users you name. Both subtypes post a message that IS VISIBLE in the record's chatter. The difference is who it reaches beyond the people you name. Args: model: the Odoo model, e.g. "sale.order". record_id: id of the record to write on. message: the body. Send PLAIN TEXT: Odoo escapes anything that arrives over RPC, so "<b>x</b>" is displayed as the literal characters `<b>x</b>`, not as bold — there is no way to pass real markup through this call, and newlines survive but are not turned into line breaks. Write the note as prose. user_ids: res.users ids to notify. They are notified each through their OWN Odoo setting, inbox or email, so naming someone is not a promise that no mail leaves. subtype: where the message lands. | subtype | visible in the chatter | emails a customer | |-----------|------------------------|-------------------| | "note" | yes, internal users | never | | "inbox" | NO — notification only | never | | "comment" | yes, everyone | **YES** | "note" posts `mail.mt_note` and is the default: measured on a real order it produced one inbox notification and zero emails. "inbox" goes through `message_notify`, which Odoo documents as the path for "messages that should not be displayed on a document" — the person is notified, the record keeps no trace. "comment" posts `mail.mt_comment` and is refused while an external follower exists, unless force=True. force: post the comment anyway, knowing those people get an email.

NameTypeReqDescription
forceboolean
messagestringyes
modelstringyes
record_idintegeryes
subtypestring
user_idsarrayyes
NameTypeReqDescription
resultstringyes

No examples provided.

read_conversation ~133

Read what was said in a Discuss conversation, newest first. This is how you answer "what did they write to me" or "what is going on in that channel". `list_message_targets` gives you the `channel_id` and says how many messages are unread. Reading does not mark anything as read: the unread counter belongs to the member record and only the user's own client clears it. Args: channel_id: the Discuss channel, from `list_message_targets`. limit: how many recent messages to return.

NameTypeReqDescription
channel_idintegeryes
limitinteger
NameTypeReqDescription
resultstringyes

No examples provided.

read_record ~222

Read one record by id, always with named fields (writing.md pattern 12). Omitting `fields` asks for a short list of state fields — never for all of them: that read is slow at best and fails at worst. `account.move` and `account.move.line` are refused here, because the structural guard wants a `move_type` filter and this tool has nowhere to put one. Use `search_read` with `[["id", "=", <id>], ["move_type", "=", "out_invoice"]]` instead. And never add up `amount_total` across records — it is in the record's own currency. `amount_total_signed` is the company-currency twin to sum. Args: model: Odoo model, e.g. "sale.order". record_id: The record's database id. fields: Field names to read. Omit for the usual state fields.

NameTypeReqDescription
fields
modelstringyes
record_idintegeryes
NameTypeReqDescription
resultstringyes

No examples provided.

required_fields ~211

List the fields Odoo demands before it will accept a `create`, with the default it would apply and how existing records actually use it. Ask this BEFORE `create_record` on a model you have not written to in this session. The answer is read from the live instance — `fields_get` plus `default_get` — never from a table in this file, so a model customised in-house reports its own requirements. The dangerous required field is the one that already carries a default: the create succeeds without you naming it and the record lands wherever the default points, with no error to notice. `crm.lead.type` is the standing example — Odoo defaults it to 'lead', and on an instance that works its pipeline as opportunities that record goes straight to a menu nobody opens. That is why the live distribution is printed beside each default. Args: model: Odoo model, e.g. "crm.lead".

NameTypeReqDescription
modelstringyes
NameTypeReqDescription
resultstringyes

No examples provided.

run_action ~125

Run a workflow method and report the state it left behind. The level follows `method`: confirming or posting is a state change, cancelling or unlinking is destructive and refused unless the server's ceiling was raised deliberately. Two behaviours come from the Writer and are worth knowing: a returned dict carrying `res_model` is a wizard to follow rather than a result, and a transition is one-way — calling it twice raises instead of doing nothing.

NameTypeReqDescription
methodstringyes
modelstringyes
record_idsarrayyes
NameTypeReqDescription
resultstringyes

No examples provided.

search_read ~359

Search and read records in one call (Odoo `search_read`). Two pitfalls this tool cannot fix for you: * `account.move` and `account.move.line` mix customer invoices, vendor bills, credit notes and raw journal entries. A domain without `move_type` is refused — add `["move_type", "=", "out_invoice"]` (or `in_invoice`, `out_refund`, `in_refund`) so the answer matches what the user sees on screen. * NEVER sum `amount_total`: it is expressed in each record's own currency, and eight foreign-currency invoices once inflated a total 11,9×. Ask for `amount_total_signed` instead — any field with a `_signed` twin is stored in company currency, and the twin is the one to add up. Args: model: Odoo model, e.g. "sale.order". domain: Odoo domain, e.g. [["state", "=", "sale"]]. fields: Field names to return. Name them: the default asks for every field, which is slow and can fail to serialise on wide models. limit: Rows to return. Hard-capped at 200. offset: Rows to skip — how to page past a truncated result. company_ids: Companies to read from, e.g. [1, 2]. On a multi-company instance, omitting this reports one company as the whole business.

NameTypeReqDescription
company_ids
domainarrayyes
fields
limitinteger
modelstringyes
offsetinteger
NameTypeReqDescription
resultstringyes

No examples provided.

send_channel_message ~146

Post to an EXISTING Discuss channel — everyone in it sees this. The channel is never created here: `list_message_targets` shows the ones that exist, and posting to a room of the wrong size is not recoverable by deleting the message afterwards. Members who are not employees of this instance — portal users, guests — are named in a refusal rather than written to, the same rule `notify_user` applies to external followers. Nothing is posted in that case. Args: channel_id: from `list_message_targets`. message: the body, plain text or simple HTML.

NameTypeReqDescription
channel_idintegeryes
messagestringyes
NameTypeReqDescription
resultstringyes

No examples provided.

send_direct_message ~189

Send a 1-to-1 Discuss message that appears in the user's chat systray. This is the tool for "tell X", "message X", "warn X". It opens the private chat with that user — reusing the existing one, `channel_get` matches on the exact pair — and posts there. The bus pushes it in real time and it persists, so a recipient who is offline finds it on their next login. It reaches them whatever their notification setting says, and sends no email at all. That is the difference from `notify_user`, which follows the recipient's preference and lands in the Inbox bell instead. Args: user_id: res.users id of the recipient — from `list_message_targets`. message: the body, plain text or simple HTML.

NameTypeReqDescription
messagestringyes
user_idintegeryes
NameTypeReqDescription
resultstringyes

No examples provided.

write_record ~108

Write field values to one record and report what actually changed. Writing the value a record already holds succeeds and changes nothing; only the before/after comparison tells that apart from a real update, so that comparison is the answer. Setting `active` to False archives the record — the same visible outcome as deleting it — and is classified destructive rather than as a plain write.

NameTypeReqDescription
modelstringyes
record_idintegeryes
valuesobjectyes
NameTypeReqDescription
resultstringyes

No examples provided.