dsh-odoo
dsh-odoo is a free, open-source, read-only DeepSeek Harness plugin for the Odoo external API.
It lets an agent inspect Odoo business data — contacts, quotations, sales orders, invoices,
project tasks, leads, stock — without changing Odoo state. One opt-in tool can create a
strictly limited draft record, and it is not even registered unless you enable it.
✅ Verified against Odoo 18 on 2026-08-27 — the official
odoo:18image reportingserver_version18.0-20260817. Most compatibility assumptions were checked on a live server; the results, and what is still unverified, are indocs/live-verification.md: only Odoo 18 was tested (8–17 and 19 were not), and only a Docker image reached directly — behind a reverse proxy, Odoo Online and Odoo.sh remain untested. The plugin speaks JSON-RPC only and needs/jsonrpcto be reachable (see Transport). When something breaks, please report the Odoo version and serie, how it is deployed (Odoo Online, Odoo.sh, self-hosted, container, and which reverse proxy sits in front), and the full output ofodoo_server_info.
Tools
| Tool | Purpose |
|---|---|
odoo_server_info |
Read the server version and the authenticated user id. |
odoo_describe_model |
List the queryable fields of one allow-listed model. |
odoo_search_read |
Run a restricted search_read on one allow-listed model. |
odoo_create_draft |
Create one draft record. Requires allowWrite: true; otherwise it is never registered. |
Transport
This plugin speaks JSON-RPC 2.0 to POST {baseUrl}/jsonrpc, so your Odoo server must expose
that endpoint (it is provided by the web module). If the endpoint is missing, redirected, or
intercepted by a proxy, every tool fails with a TRANSPORT_UNSUPPORTED error that says so.
XML-RPC is not implemented.
Model availability
The 14 allow-listed models are not guaranteed to exist on every Odoo. Only the base models
are always present; the rest come from business modules a stock Odoo does not install.
| Module | Allow-listed models | Installed by default |
|---|---|---|
base |
res.partner, res.users, res.company |
yes |
product |
product.product, product.template |
no |
sale / sale_management |
sale.order, sale.order.line |
no |
purchase |
purchase.order |
no |
account |
account.move, account.move.line |
no |
project |
project.project, project.task |
no |
crm |
crm.lead |
no |
stock |
stock.quant |
no |
On a stock Odoo 18 with no business modules, querying sale.order fails with
ODOO_VALIDATION_ERROR carrying the upstream reason Object sale.order doesn't exist. Call
odoo_describe_model first to confirm a model is available on your instance.
Requirements
- DeepSeek Harness with compatible
@deepseek-ai/dsh-toolsAPIs - Node.js 22.19 or newer in the 22.x line, or Node.js 24 or newer
- Bun 1.3.5 or newer when installing from GitHub source or developing locally
- An Odoo URL, database name, login, and API key (or password) with access to the models you query
Configuration
Environment variables are recommended so credentials do not appear in a profile patch:
export ODOO_URL='https://odoo.example.com'
export ODOO_DB='production'
export ODOO_USERNAME='integration@example.com'
export ODOO_API_KEY='your-api-key'
Plugin config takes precedence over environment variables:
| Config | Environment fallback | Default |
|---|---|---|
baseUrl |
ODOO_URL |
required |
db |
ODOO_DB |
required |
username |
ODOO_USERNAME |
required |
apiKey |
ODOO_API_KEY |
required |
companyId |
ODOO_COMPANY_ID |
unset |
allowWrite |
none (deliberately) | false |
locale |
none | en (en / zh-TW / zh-CN / ja) |
defaultLimit |
none | 20 (1–100) |
requestTimeoutMs |
none | 30000 (1–300000) |
maxResponseBytes |
none | 1000000 (1–52428800) |
Credentials are only required when a tool actually runs: installing the plugin without filling
them in does not break profile loading. locale switches tool and parameter descriptions;
tool names and error messages always stay in English.
Safety
- Read-only by default. No
write,unlink, or workflow actions exist in this release. - Model allow list. Queries are limited to 14 standard models:
res.partner,res.users,res.company,product.product,product.template,sale.order,sale.order.line,purchase.order,account.move,account.move.line,project.project,project.task,crm.lead,stock.quant. - No relational traversal. Domain field names may not contain dots. To filter by a related
record, query the related model first and then filter with
('partner_id','in',[ids]). This keeps the allow list an actual capability boundary instead of a suggestion. - No binary fields. Fields whose Odoo type is
binaryare rejected, and default field sets never include one. - Bounded responses. Default fields per model,
limit≤ 100,offset + limit≤ 10000, single string values truncated at 2000 characters, and a hard byte cap on every response. - Only non-archived records are returned; the
active_testcontext is not exposed. - Draft creation is opt-in and fixed.
sale.orderis always created withstate=draft;project.taskmay not specifystateorstage_id, so Odoo applies its own default stage. Only an allow-listed subset of fields is accepted, and one-to-many commands are rejected.
Non-goals for 0.1
- No business wrapper tools (
list_customers,list_quotations, …). They depend on field assumptions that cannot be verified without a live Odoo, and a wrong assumption returns an empty result instead of an error — the worst failure mode for an agent. Deferred to 0.2. - No updates, deletions, or workflow transitions; no attachments or report generation.
- No XML-RPC transport, no model discovery, no multi-database switching, no cursor pagination.
Development
bun install
bun run lint
bun run typecheck
bun run test
bun run build
scripts/smoke-odoo.sh runs a manual end-to-end check against a real server; it is deliberately
excluded from CI.
License
MIT
No comments yet. Be the first to write one.