odoo-assistant 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. odoo_assistant-0.1.0/.gitignore +14 -0
  2. odoo_assistant-0.1.0/.gitleaks.toml +6 -0
  3. odoo_assistant-0.1.0/.pre-commit-config.yaml +5 -0
  4. odoo_assistant-0.1.0/AGENTS.md +81 -0
  5. odoo_assistant-0.1.0/CHANGELOG.md +27 -0
  6. odoo_assistant-0.1.0/CONTRIBUTING.md +60 -0
  7. odoo_assistant-0.1.0/LICENSE +21 -0
  8. odoo_assistant-0.1.0/PKG-INFO +304 -0
  9. odoo_assistant-0.1.0/README.md +279 -0
  10. odoo_assistant-0.1.0/SECURITY.md +38 -0
  11. odoo_assistant-0.1.0/pyproject.toml +64 -0
  12. odoo_assistant-0.1.0/references_public/SKILL.md +413 -0
  13. odoo_assistant-0.1.0/references_public/collaboration.md +291 -0
  14. odoo_assistant-0.1.0/references_public/deletion.md +129 -0
  15. odoo_assistant-0.1.0/references_public/documents.md +287 -0
  16. odoo_assistant-0.1.0/references_public/payments.md +237 -0
  17. odoo_assistant-0.1.0/references_public/recipes.md +429 -0
  18. odoo_assistant-0.1.0/references_public/writing.md +409 -0
  19. odoo_assistant-0.1.0/scripts/check-release-consistency.py +46 -0
  20. odoo_assistant-0.1.0/scripts/sync-from-skill.sh +58 -0
  21. odoo_assistant-0.1.0/server.json +64 -0
  22. odoo_assistant-0.1.0/skills-lock.json +23 -0
  23. odoo_assistant-0.1.0/src/odoo_assistant/__init__.py +1 -0
  24. odoo_assistant-0.1.0/src/odoo_assistant/odoo_scripts/__init__.py +0 -0
  25. odoo_assistant-0.1.0/src/odoo_assistant/odoo_scripts/census.py +368 -0
  26. odoo_assistant-0.1.0/src/odoo_assistant/odoo_scripts/collaboration.py +350 -0
  27. odoo_assistant-0.1.0/src/odoo_assistant/odoo_scripts/documents.py +442 -0
  28. odoo_assistant-0.1.0/src/odoo_assistant/odoo_scripts/explore_module.py +439 -0
  29. odoo_assistant-0.1.0/src/odoo_assistant/odoo_scripts/odoo_client.py +571 -0
  30. odoo_assistant-0.1.0/src/odoo_assistant/odoo_scripts/query.py +136 -0
  31. odoo_assistant-0.1.0/src/odoo_assistant/odoo_scripts/safety_layer.py +226 -0
  32. odoo_assistant-0.1.0/src/odoo_assistant/odoo_scripts/view_first.py +174 -0
  33. odoo_assistant-0.1.0/src/odoo_assistant/odoo_scripts/write_patterns.py +407 -0
  34. odoo_assistant-0.1.0/src/odoo_assistant/paths.py +57 -0
  35. odoo_assistant-0.1.0/src/odoo_assistant/resources.py +116 -0
  36. odoo_assistant-0.1.0/src/odoo_assistant/server.py +200 -0
  37. odoo_assistant-0.1.0/src/odoo_assistant/server_errors.py +175 -0
  38. odoo_assistant-0.1.0/src/odoo_assistant/server_safety.py +142 -0
  39. odoo_assistant-0.1.0/src/odoo_assistant/tools_collab.py +300 -0
  40. odoo_assistant-0.1.0/src/odoo_assistant/tools_discuss.py +342 -0
  41. odoo_assistant-0.1.0/src/odoo_assistant/tools_evolution.py +246 -0
  42. odoo_assistant-0.1.0/src/odoo_assistant/tools_read.py +347 -0
  43. odoo_assistant-0.1.0/src/odoo_assistant/tools_write.py +218 -0
  44. odoo_assistant-0.1.0/tests/SPIKE_NOTES.md +127 -0
  45. odoo_assistant-0.1.0/tests/__init__.py +0 -0
  46. odoo_assistant-0.1.0/tests/conftest.py +272 -0
  47. odoo_assistant-0.1.0/tests/spike_client.py +50 -0
  48. odoo_assistant-0.1.0/tests/spike_server.py +25 -0
  49. odoo_assistant-0.1.0/tests/test_connection.py +206 -0
  50. odoo_assistant-0.1.0/tests/test_errors.py +262 -0
  51. odoo_assistant-0.1.0/tests/test_evolution.py +264 -0
  52. odoo_assistant-0.1.0/tests/test_fixtures.py +137 -0
  53. odoo_assistant-0.1.0/tests/test_formatting.py +86 -0
  54. odoo_assistant-0.1.0/tests/test_integration.py +728 -0
  55. odoo_assistant-0.1.0/tests/test_paths.py +72 -0
  56. odoo_assistant-0.1.0/tests/test_production_guard.py +121 -0
  57. odoo_assistant-0.1.0/tests/test_protocol.py +190 -0
  58. odoo_assistant-0.1.0/tests/test_resources.py +195 -0
  59. odoo_assistant-0.1.0/tests/test_safety.py +317 -0
  60. odoo_assistant-0.1.0/tests/test_server_registration.py +43 -0
  61. odoo_assistant-0.1.0/tests/test_tools_collab.py +510 -0
  62. odoo_assistant-0.1.0/tests/test_tools_read.py +366 -0
  63. odoo_assistant-0.1.0/tests/test_tools_write.py +456 -0
  64. odoo_assistant-0.1.0/tests/test_wheel_e2e.py +178 -0
  65. odoo_assistant-0.1.0/uv.lock +1114 -0
@@ -0,0 +1,14 @@
1
+ .venv/
2
+ dist/
3
+ __pycache__/
4
+ *.pyc
5
+ .pytest_cache/
6
+ .omo/
7
+ .codegraph
8
+ .DS_Store
9
+ /tmp-install/
10
+ .env*
11
+ *.local
12
+ .vscode/
13
+ # carries a real API key for local MCP testing
14
+ .opencode/opencode.json
@@ -0,0 +1,6 @@
1
+ [allowlist]
2
+ description = "Allow documented example credentials in vendored documentation"
3
+ paths = [
4
+ '''docs/json2-source/docs/SPA_AUTH_GUIDE\.md$''',
5
+ '''docs/mcp-spec/docs/2026-07-28/tutorials/security/authorization\.md$''',
6
+ ]
@@ -0,0 +1,5 @@
1
+ repos:
2
+ - repo: https://github.com/gitleaks/gitleaks
3
+ rev: v8.24.2
4
+ hooks:
5
+ - id: gitleaks
@@ -0,0 +1,81 @@
1
+ # AGENTS.md — odoo-assistant-mcp
2
+
3
+ MCP server (stdio) that wraps 9 verified Odoo scripts as **19 tools + 8
4
+ resources**. Shipped and complete; published on PyPI as version 0.1.0 and
5
+ installable with `uvx odoo-assistant`.
6
+
7
+ ## Read before coding
8
+
9
+ - `docs/PRD.md` — the spec: tools (§5B), auth (§6), safety mapping (§7), packaging (§10). Written before the build, so it trails the code: it says 14 tools and two sections are marked superseded. Trust the code first.
10
+ - `docs/mcp-spec/` — the ENTIRE MCP 2026-07-28 spec, vendored (144 files). Consult it instead of the web.
11
+ - `docs/RELEASE-CHECKLIST.md` — the only work left, and all of it needs a human: PyPI Trusted Publishing, then the tag.
12
+ - `docs/json2-source/` — the Odoo module behind `/json/2`. `odoo_client.py` probes that transport first and falls back to XML-RPC, which Odoo 19 deprecates and 22 removes, so this is the reference for the path that becomes primary.
13
+ - `references/BUILD-STATE.md` — sanity figures for the dev instance
14
+
15
+ ## Commands
16
+
17
+ ```bash
18
+ uv run pytest # 225 tests. addopts already excludes live+wheel
19
+ uv build && uv run pytest -m wheel # 3 tests. needs dist/ AND network (uvx)
20
+ uv run pytest -m live # 6 tests. needs the env vars below
21
+ ```
22
+
23
+ **Never write `-m "not live"`.** A `-m` on the command line replaces `addopts`
24
+ instead of narrowing it, so that spelling silently re-enables the `wheel` tests
25
+ and they fail on a clean checkout with no `dist/`. CI spells it in full:
26
+ `-m "not live and not wheel"`.
27
+
28
+ Protocol check: `npx @modelcontextprotocol/inspector` against the server.
29
+
30
+ ## Hard rules
31
+
32
+ - **Never rewrite `src/odoo_assistant/odoo_scripts/`.** Those 9 scripts are verified against a live instance and are the source of truth; the server is a thin wrapper importing them. They came from an agent skill that has been retired, so this repo is the canonical copy — `scripts/sync-from-skill.sh` has no upstream left and exits 1. Fix a bug there only with a live test proving it; otherwise adapt the wrapper.
33
+ - Scripts are **flat modules**: each does `sys.path.insert(0, <own dir>)` + `from odoo_client import ...`. Keep that import style working when packaging.
34
+ - SDK: `from mcp.server import MCPServer` (SDK 2.0, pinned `mcp[cli]>=2.0.0,<3`). `MCPServer(...)` defaults `version` to `""` — pass it explicitly or every host displays an empty version.
35
+ - **Module decomposition**: `server.py` stays thin. Tools and resources live in `tools_read.py` / `tools_write.py` / `tools_collab.py` / `tools_discuss.py` / `tools_evolution.py` / `resources.py`, each exposing `register(mcp)`, wired by `server._register_all()`. Adding a tool means updating the three tests that assert the exact tool set. `tools_collab.py` notifies ABOUT a record (Inbox bell); `tools_discuss.py` is user-to-user conversation (chat systray) — "message X" is the second, and its `channel_get` is the one method the verified `safety_layer.py` was extended to admit (L1).
36
+ - **`explore_module.REF_DIR` redirect** needs the package-qualified import (`from odoo_assistant.odoo_scripts import explore_module`) so the patch is visible to every importer. See `tools_evolution.py`'s docstring.
37
+ - **Wheel bundles `references_public/` only** (scrubbed, generic). `references/` holds the instance-specific set and is excluded.
38
+ - stdio transport: **nothing may reach stdout** but the JSON-RPC stream. Diagnostics go to stderr.
39
+ - Every write passes `server_safety.gate()` (L0–L5). L4/L5 refuse by default; `ODOO_MCP_MAX_LEVEL` moves the bar and refuses startup if invalid. Host consent dialogs are untrusted — **the gate is the only enforcement point**.
40
+
41
+ ## Where guidance actually lands (measured)
42
+
43
+ Two controlled runs of a fresh host session showed the `odoo://skill` resource
44
+ being read **zero** times, and removing the equivalent global skill changed no
45
+ answer. What routed behaviour both times was a **tool docstring** — it becomes
46
+ the tool description, which every host puts in context unconditionally.
47
+
48
+ So a rule an agent must follow belongs in the tool's docstring, not in a
49
+ resource and not in a reference file. Structural guards in `safety_layer.py`
50
+ are the only thing stronger, because they refuse instead of advise.
51
+
52
+ ## Odoo landmines (silent wrong results, not errors)
53
+
54
+ Full list: `references/SKILL.md` (8 rules) + `references/writing.md` (12 patterns).
55
+
56
+ - Odoo commits **before** serialising the response: an exception can mean "committed but unserializable" (`OdooExecutedButUnserializable`). **Never retry** — re-read. Blind retries once created duplicate invoices. Seen live: `crm.lead.action_set_lost` takes this path.
57
+ - A returned dict carrying `res_model` is a wizard to follow, not a result.
58
+ - `account.move` / `account.move.line` without `move_type` mixes invoices, bills and journal entries (3.613 vs 373). `check_guards()` blocks it structurally.
59
+ - Sum `*_signed` fields, never `amount_total` (multi-currency once inflated a total 11,9×).
60
+ - Multi-company: pass `context={"allowed_company_ids": [...]}` or you report one company as the whole business.
61
+ - **`default_get` is where records go missing.** `crm.lead.type` defaults to `'lead'` on an instance whose pipeline is opportunities; `account.move.move_type` defaults to `'entry'`, so a "new invoice" is a raw journal entry. The `required_fields` tool exists to surface this — call it before a create.
62
+ - **Archiving reads like deletion.** `action_set_lost` sets `active=False`; a later plain search returns `[]`. Add `["active", "in", [true, false]]` before concluding a record is gone.
63
+ - **`phone_sanitized` is one value per record**, computed from `mobile` first and `phone` second — not per field. It stays `False`, with no error, when the number has no `+` prefix and the record no `country_id`. Write E.164 yourself.
64
+ - Wrong `ODOO_USER` makes `authenticate()` return `False` rather than raise — it reads like a permission error. Omitting it entirely is supported and takes the discovery path (up to 59 probes, fails at uid >= 60).
65
+ - `create()` returns a list, not an int. Methods starting with `_` are always rejected.
66
+ - Idempotency: `Writer.create(..., unique_on=[...])`, chains via `Writer.step()`. A write is done only when a re-read proves it.
67
+
68
+ ## Live and local testing
69
+
70
+ - Env: required are `ODOO_BASE_URL` (no trailing slash) and `ODOO_API_KEY` only. `ODOO_DB` is discovered (mandatory only when the instance serves several — the error names them), `ODOO_USER` is discovered from the key. **API key only** — passwords were removed deliberately.
71
+ - Write scenarios need `ODOO_MCP_ALLOW_LIVE_WRITE=1`; that variable is test-suite-only and must never appear in host config.
72
+ - Dev instance `persevida_dev18` (Odoo 18 Enterprise, companies ES+CZ, XML-RPC, destructive tests allowed). `odoo_client.py` refuses writes to hosts listed in `ODOO_MCP_PROTECTED_HOSTS` (env, empty by default — no host is hardcoded).
73
+ - Cleanup archives rather than deletes — `Writer.can()` refuses partner unlink — so `MCP Test %` residue on the dev instance is expected.
74
+ - Local host config lives in `.opencode/opencode.json`, **gitignored because it holds a real API key**. It runs the server from source (`uv run --directory <repo> odoo-assistant`) for development convenience, and raises `timeout` from opencode's 5000 ms default, which `instance_overview` exceeds.
75
+ - Smoke-test a script directly: `python3 src/odoo_assistant/odoo_scripts/query.py --url http://host:8069 --key <API_KEY>`
76
+
77
+ ## references/
78
+
79
+ Generated by `explore_module.py`, never hand-written. Everything above
80
+ `## NOTES` is rebuilt on each run — hand-edit only the `## NOTES` section.
81
+ Served as MCP resources (`odoo://skill`, `odoo://ref/*`).
@@ -0,0 +1,27 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.1.0] - 2026-08-18
9
+
10
+ This is the first published release of the Odoo Assistant MCP Server.
11
+
12
+ ### Added
13
+ - **MCP Server Core**: Implemented `MCPServer` skeleton with environment credentials validation and stderr logging.
14
+ - **Safety Layer**: Dynamic classification gate (L0 to L5) with configurable `ODOO_MCP_MAX_LEVEL` ceiling.
15
+ - **Error Handling**: Custom error models, `OdooExecutedButUnserializable` handling, and output truncation strategy.
16
+ - **Read Tools**: Implemented `search_read`, `read_record`, `count_records`, and `instance_overview` with company context and caps.
17
+ - **Write Tools**: Implemented `create_record`, `write_record`, `run_action`, and `cancel_record` with idempotency and verification.
18
+ - **Collaboration & Document Tools**: Implemented `notify_user`, `create_activity`, `download_docs`, and `generate_pdf` with audience guards.
19
+ - **Discuss Tools**: Implemented `list_message_targets`, `read_conversation`, `send_direct_message`, and `send_channel_message` to support user-to-user messaging.
20
+ - **Evolution Tools**: Implemented `explore_module` with persistence redirect and `list_known_modules`.
21
+ - **Resources**: Exposed `odoo://skill` and `odoo://ref/*` from the bundled reference set.
22
+ - **Testing**: Added mock-based unit tests, scripted MCP Inspector suite, and opt-in live integration suite with cancel-based cleanup.
23
+ - **CI/CD**: Configured tag-triggered sequential PyPI and MCP Registry publishing pipeline using GitHub OIDC.
24
+ - **Documentation**: Added PyPI README, host configuration examples, security policy, and developer guidelines.
25
+ - **Credentials**: Only `ODOO_BASE_URL` and `ODOO_API_KEY` are required; the database and the login are discovered from the key.
26
+ - **Paths**: A single per-OS data directory for caching and persistence, with `ODOO_MCP_DATA_DIR` to override it.
27
+ - **Notifications**: `notify_user` offers three subtypes — `note` (internal, visible in the chatter), `inbox` (notification only, invisible on the record) and `comment` (emails every follower).
@@ -0,0 +1,60 @@
1
+ # Contributing to Odoo Assistant MCP Server
2
+
3
+ Thank you for contributing to the Odoo Assistant MCP Server. Please follow these guidelines to set up your development environment, run tests, and submit changes.
4
+
5
+ ## Development Environment Setup
6
+
7
+ This project uses `uv` for Python package management.
8
+
9
+ 1. **Install uv**:
10
+ Follow the instructions at [astral.sh/setup-uv](https://astral.sh/setup-uv) to install `uv`.
11
+
12
+ 2. **Clone the repository**:
13
+ ```bash
14
+ git clone https://github.com/singleflo/odoo-assistant-mcp.git
15
+ cd odoo-assistant-mcp
16
+ ```
17
+
18
+ 3. **Sync dependencies**:
19
+ ```bash
20
+ uv sync
21
+ ```
22
+
23
+ ## Hard Rules for Code Changes
24
+
25
+ * **Treat `src/odoo_assistant/odoo_scripts/` as verified code, not as your working surface.** These 9 scripts are verified against a live Odoo instance and are the source of truth; the MCP server is a thin wrapper importing them. They came from an agent skill that has since been retired, so this repository is now the canonical copy — there is no upstream to sync from. Fix a bug here only with a live-instance test proving it, and never refactor them to suit the wrapper: adapt the wrapper instead.
26
+ * **Keep server.py thin.** Tools and resources must live in their respective modules (`tools_read.py`, `tools_write.py`, `tools_collab.py`, `tools_evolution.py`, `resources.py`) and expose a `register(mcp)` function.
27
+ * **No stdout printing.** The stdio transport uses stdout for JSON-RPC communication. Any diagnostic prints must go to stderr.
28
+
29
+ ## Running Tests
30
+
31
+ We use `pytest` for testing.
32
+
33
+ ### Non-Live Tests (Default)
34
+ To run the unit tests and mock-based integration tests:
35
+ ```bash
36
+ uv run pytest tests/ -m "not live"
37
+ ```
38
+
39
+ ### Live Tests
40
+ To run tests against a live Odoo instance, you must opt-in by providing the connection details and setting the appropriate environment variables:
41
+
42
+ 1. **Read-only live tests**:
43
+ ```bash
44
+ export ODOO_BASE_URL="https://your-odoo-instance.com"
45
+ export ODOO_API_KEY="your-api-key"
46
+ export ODOO_DB="your-db"
47
+ export ODOO_USER="your-user" # Optional: omitted, the login is discovered from the key
48
+ uv run pytest tests/
49
+ ```
50
+
51
+ 2. **Write live tests**:
52
+ By default, live tests that perform write operations are skipped to prevent accidental modifications to your instance. To enable them, set `ODOO_MCP_ALLOW_LIVE_WRITE=1`:
53
+ ```bash
54
+ export ODOO_MCP_ALLOW_LIVE_WRITE=1
55
+ uv run pytest tests/
56
+ ```
57
+
58
+ ## Commit Conventions
59
+
60
+ We follow standard git commit message conventions. Please write concise, descriptive commit messages matching the repository style. Stage only intended files and never commit secrets.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Roberto Crotti
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,304 @@
1
+ Metadata-Version: 2.5
2
+ Name: odoo-assistant
3
+ Version: 0.1.0
4
+ Summary: An Odoo virtual employee via MCP: query, create, modify records, run workflows
5
+ Project-URL: Homepage, https://github.com/singleflo/odoo-assistant-mcp
6
+ Project-URL: Repository, https://github.com/singleflo/odoo-assistant-mcp
7
+ Project-URL: Issues, https://github.com/singleflo/odoo-assistant-mcp/issues
8
+ Project-URL: MCPRegistry, https://registry.modelcontextprotocol.io/servers/io.github.singleflo/odoo-assistant
9
+ Author-email: Roberto Crotti <bo@fl1.cz>
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: ai,claude,erp,mcp,model-context-protocol,odoo
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Software Development :: Libraries
22
+ Requires-Python: >=3.10
23
+ Requires-Dist: mcp[cli]<3,>=2.0.0
24
+ Description-Content-Type: text/markdown
25
+
26
+ <!-- mcp-name: io.github.singleflo/odoo-assistant -->
27
+ # Odoo Assistant MCP Server
28
+
29
+ An Odoo virtual employee via the Model Context Protocol (MCP). This server exposes Odoo's business logic, records, and workflows to LLMs, allowing them to query, create, update, and act on Odoo data safely.
30
+
31
+ ## Quickstart
32
+
33
+ ### 1. Install
34
+
35
+ Run the server directly:
36
+
37
+ ```bash
38
+ uvx odoo-assistant
39
+ ```
40
+
41
+ Or install it into your environment:
42
+
43
+ ```bash
44
+ uv pip install odoo-assistant
45
+ ```
46
+
47
+ Installing from source for development remains possible:
48
+
49
+ ```bash
50
+ uv pip install git+https://github.com/singleflo/odoo-assistant-mcp
51
+ ```
52
+
53
+ ### 2. Configure Environment Variables
54
+ Two variables are required; everything else is discovered or has a default:
55
+
56
+ * `ODOO_BASE_URL`: The base URL of your Odoo instance (e.g., `https://mycompany.odoo.com`).
57
+ * `ODOO_API_KEY`: The Odoo API key (Odoo 14+, generate under Settings > Users > API Keys > New). An account password is not accepted: a key is per-user, scoped and revocable on its own.
58
+
59
+ Optional configuration:
60
+ * `ODOO_DB`: The database name. Discovered automatically when the instance serves exactly one database; required when it serves several — the error names them all.
61
+ * `ODOO_USER`: The login. Discovered from the key at the cost of up to 59 extra round trips; discovery fails if the key owner's uid is 60 or higher. Setting it saves the probe.
62
+ * `ODOO_MCP_MAX_LEVEL`: The highest safety level this server may execute, `0` to `4` (default: `3`). This is how you make the server read-only or let it delete — see [Choosing the ceiling](#choosing-the-ceiling).
63
+ * `ODOO_MCP_PROTECTED_HOSTS`: Comma-separated hosts this server refuses to *write* to (empty by default — no host is baked into the package). A listed host still allows reads; writing needs `ODOO_ALLOW_PROD_WRITE=yes` as a deliberate override.
64
+
65
+ ## Safety Layer
66
+
67
+ Every write and action passes through a dynamic safety classifier before reaching Odoo. Operations are classified into levels L0 to L5:
68
+
69
+ | Level | Name | Description | Default Status |
70
+ |---|---|---|---|
71
+ | **L0** | `L0_READ` | Read-only queries (`search_read`, `read`, `search_count`). | Allowed |
72
+ | **L1** | `L1_WRITE` | Single record writes and creations. | Allowed |
73
+ | **L2** | `L2_BATCH` | Batch writes affecting multiple records. | Allowed |
74
+ | **L3** | `L3_STATE_CHANGE` | Workflow state transitions (e.g., confirming orders, posting invoices). | Allowed |
75
+ | **L4** | `L4_DESTRUCTIVE` | Destructive operations (e.g., `unlink`, `action_cancel`, archiving). | Blocked |
76
+ | **L5** | `L5_PRIVATE` / `L5_UNKNOWN` | Private methods or unknown operations. | Blocked |
77
+
78
+ ### Choosing the ceiling
79
+
80
+ `ODOO_MCP_MAX_LEVEL` sets the highest level this server may execute. Each value
81
+ is cumulative — it permits its own level and everything below:
82
+
83
+ | Value | What it permits |
84
+ |---|---|
85
+ | `0` | Reads only. |
86
+ | `1` | + single-record writes and creations. |
87
+ | `2` | + batches above 5 records. |
88
+ | `3` | **Default.** + confirming orders, posting invoices, sending mail. |
89
+ | `4` | + `unlink`, `action_cancel`, archiving. |
90
+ | `5` | Accepted, but identical to `4` in effect — see below. |
91
+
92
+ Two behaviours are worth knowing before you pick a number:
93
+
94
+ * **`5` does not unlock L5.** Both L5 variants are refused before the ceiling is
95
+ ever read. `L5_PRIVATE` is refused because Odoo itself rejects every method
96
+ starting with `_`, so no ceiling could deliver it; `L5_UNKNOWN` is refused
97
+ because a method nobody classified has, by definition, unreviewed effects. The
98
+ way to allow such a method is to add it to `WRITE_L1`/`L3`/`L4` in
99
+ `safety_layer.py` — in code, reviewed — never through configuration.
100
+ * **An invalid value refuses startup.** `ODOO_MCP_MAX_LEVEL="O"` raises rather
101
+ than falling back to the default, because the fallback is write-capable: a
102
+ typo must not hand you a writing server you believed was read-only.
103
+
104
+ The ceiling is set out of band, by a human, and read from the process
105
+ environment at startup. The model running against this server cannot raise it;
106
+ when a call exceeds the ceiling the refusal names the level required, so the
107
+ agent can explain what the operation would change and leave the decision to you.
108
+
109
+ Note that this is the authority of *this server*, not of the account. An agent
110
+ with shell access can always bypass an MCP server by invoking Odoo directly. A
111
+ limit that must hold regardless of the client belongs in the Odoo access rights
112
+ of the user the API key belongs to, where the Odoo server enforces it.
113
+
114
+ ## Odoo Version Support
115
+
116
+ Odoo 14.0 is the absolute minimum supported version because this server authenticates using API keys only, which do not exist in Odoo 13 or earlier.
117
+
118
+ | Odoo Version | API Keys | XML-RPC | Officially Maintained (Aug 2026) | Support Level / Notes |
119
+ |---|---|---|---|---|
120
+ | **≤ 13.0** | **No** | Yes | No | **Unsupported**. Cannot authenticate with this server. |
121
+ | **14.0** | **Yes** | Yes | No | Protocol-compatible. Untested against a live instance. |
122
+ | **15.0** | Yes | Yes | No | Protocol-compatible. Untested against a live instance. |
123
+ | **16.0** | Yes | Yes | No | Protocol-compatible. Untested against a live instance. |
124
+ | **17.0** | Yes | Yes | **Yes** (until Sep 2026) | Protocol-compatible. Untested against a live instance. |
125
+ | **18.0** | Yes | Yes | **Yes** (until Sep 2027) | **Primary target**. Verified and fully supported against a live instance. |
126
+ | **19.0** | Yes | Yes | **Yes** (until Sep 2028) | Protocol-compatible. Untested against a live instance. API keys require description and expiry (max 3 months). |
127
+
128
+ ### API Key Generation Path
129
+ To generate an API key, log in to your Odoo instance and navigate to:
130
+ **Preferences / My Profile → Account Security → New API Key**
131
+
132
+ ### Transport & Deprecation Note
133
+ The client automatically detects if the native JSON-2 API is available at `/json/2/<model>/<method>` (which uses `Authorization: bearer <API_KEY>`) and falls back to XML-RPC if it is not. Please note that XML-RPC and JSON-RPC are deprecated in Odoo 19 and scheduled for removal in Odoo 22.
134
+
135
+ ### Sources
136
+ - [Odoo 14.0 External API Documentation](https://www.odoo.com/documentation/14.0/developer/reference/external_api.html) (API keys introduction)
137
+ - [Odoo 19.0 External API Documentation](https://www.odoo.com/documentation/19.0/developer/reference/external_api.html) (JSON-2)
138
+ - [Odoo 19.0 External RPC API Documentation](https://www.odoo.com/documentation/19.0/developer/reference/external_rpc_api.html) (XML-RPC deprecation)
139
+ - [Odoo Standard & Extended Support Policy](https://www.odoo.com/documentation/19.0/administration/standard_extended_support.html) (Support timelines)
140
+
141
+ ## Tools and Resources
142
+
143
+ The server exposes 19 tools and 2 resource types:
144
+
145
+ ### Tools
146
+
147
+ 1. `search_read`: Search and read records in one call (Odoo `search_read`).
148
+ 2. `read_record`: Read one record by id, always with named fields.
149
+ 3. `count_records`: Count the records matching a domain (Odoo `search_count`).
150
+ 4. `instance_overview`: Summarise the connected instance: version, companies, volumes per area, in-house modules, anomalies.
151
+ 5. `required_fields`: List what Odoo demands before a `create` on a model, the default it would apply, and how existing records actually use it.
152
+ 6. `create_record`: Create a record, reusing an existing match when `unique_on` is given.
153
+ 7. `write_record`: Write field values to one record and report what actually changed.
154
+ 8. `run_action`: Run a workflow method and report the state it left behind.
155
+ 9. `cancel_record`: Cancel a record through `action_cancel`, following the wizard it returns.
156
+ 10. `notify_user`: Notify users on a record's chatter. Internal by default.
157
+ 11. `create_activity`: Schedule an activity: the only notification that carries a deadline.
158
+ 12. `download_docs`: Save every document of a record to disk, chatter files included.
159
+ 13. `generate_pdf`: Render the PDF of a record and return where it was saved.
160
+ 14. `list_message_targets`: List who can be messaged and where — internal users with presence (online/away/offline) and the caller's open conversations. Ask this before sending.
161
+ 15. `read_conversation`: Read a Discuss conversation, newest first.
162
+ 16. `send_direct_message`: Send a 1-to-1 Discuss message that appears in the user's chat systray in real time — no email, reaches them whatever their notification setting says.
163
+ 17. `send_channel_message`: Post to an existing Discuss channel, refusing a room that holds a non-employee.
164
+ 18. `explore_module`: Discover a module's structure by interrogating the live instance.
165
+ 19. `list_known_modules`: List the modules this server has learned: name, generation date, records.
166
+
167
+ Tools 10-11 (`notify_user`, `create_activity`) notify ABOUT a record and land
168
+ in the Inbox bell; tools 14-17 are Discuss conversations that land in the chat
169
+ systray. "Message user X" is the second kind — `send_direct_message`, not
170
+ `notify_user`.
171
+
172
+ ### Resources
173
+
174
+ * `odoo://skill`: Access the Odoo assistant skill instructions.
175
+ * `odoo://ref/*`: Access generated reference documentation for explored modules.
176
+
177
+ ## Host Configuration Examples
178
+
179
+ Every example below carries only what matters: the two required variables, and
180
+ the ceiling — the one setting that decides whether this server can write, made
181
+ visible in the file the human owns. The database and the login are discovered,
182
+ and `3` is the ceiling's default. Note the **quotes**: environment values are
183
+ strings.
184
+
185
+ ### Claude Desktop
186
+ Add this to your `claude_desktop_config.json`:
187
+ ```json
188
+ {
189
+ "mcpServers": {
190
+ "odoo-assistant": {
191
+ "command": "uvx",
192
+ "args": [
193
+ "odoo-assistant"
194
+ ],
195
+ "env": {
196
+ "ODOO_BASE_URL": "https://mycompany.odoo.com",
197
+ "ODOO_API_KEY": "your-api-key-here",
198
+ "ODOO_MCP_MAX_LEVEL": "3"
199
+ }
200
+ }
201
+ }
202
+ }
203
+ ```
204
+
205
+ ### Cursor
206
+ Add this to your `.cursor/mcp.json` or configure it in the Cursor settings UI:
207
+ ```json
208
+ {
209
+ "mcpServers": {
210
+ "odoo-assistant": {
211
+ "command": "uvx",
212
+ "args": [
213
+ "odoo-assistant"
214
+ ],
215
+ "env": {
216
+ "ODOO_BASE_URL": "https://mycompany.odoo.com",
217
+ "ODOO_API_KEY": "your-api-key-here",
218
+ "ODOO_MCP_MAX_LEVEL": "3"
219
+ }
220
+ }
221
+ }
222
+ }
223
+ ```
224
+
225
+ ### VS Code Copilot
226
+ Add this to your VS Code `settings.json`:
227
+ ```json
228
+ {
229
+ "mcp.servers": {
230
+ "odoo-assistant": {
231
+ "command": "uvx",
232
+ "args": [
233
+ "odoo-assistant"
234
+ ],
235
+ "env": {
236
+ "ODOO_BASE_URL": "https://mycompany.odoo.com",
237
+ "ODOO_API_KEY": "your-api-key-here",
238
+ "ODOO_MCP_MAX_LEVEL": "3"
239
+ }
240
+ }
241
+ }
242
+ }
243
+ ```
244
+
245
+ ### opencode
246
+ Add this to `opencode.json` or `.opencode/opencode.json` in your project, or to
247
+ `~/.config/opencode/opencode.json` to make the server available everywhere:
248
+ ```json
249
+ {
250
+ "$schema": "https://opencode.ai/config.json",
251
+ "mcp": {
252
+ "odoo-assistant": {
253
+ "type": "local",
254
+ "enabled": true,
255
+ "command": [
256
+ "uvx",
257
+ "odoo-assistant"
258
+ ],
259
+ "timeout": 120000,
260
+ "environment": {
261
+ "ODOO_BASE_URL": "https://mycompany.odoo.com",
262
+ "ODOO_API_KEY": "your-api-key-here",
263
+ "ODOO_MCP_MAX_LEVEL": "3"
264
+ }
265
+ }
266
+ }
267
+ }
268
+ ```
269
+
270
+ opencode's shape differs from the hosts above in ways it rejects outright: the
271
+ key is `mcp` (not `mcpServers`), `type` is required, `command` is a single array
272
+ holding the program *and* its arguments (there is no separate `args`), and the
273
+ environment block is `environment` (not `env`).
274
+
275
+ Set `timeout` deliberately. It defaults to **5000 ms**, and the first call of a
276
+ session pays for authentication plus, for `instance_overview`, dozens of XML-RPC
277
+ round trips — comfortably past five seconds against a real instance.
278
+
279
+ opencode reads its config once at startup and does not hot-reload it, so **quit
280
+ and restart** after editing. Anything you change here — the ceiling included —
281
+ takes effect only on the next launch.
282
+
283
+ ### ChatGPT (Custom Connectors)
284
+ To connect this server to ChatGPT via Custom Connectors:
285
+ 1. Go to Settings → Connectors → Add Connector.
286
+ 2. Enter the server URL or select from the Registry.
287
+ 3. Enter your Odoo credentials when prompted.
288
+
289
+ ### Hermes
290
+ Add the server using the Hermes CLI:
291
+ ```bash
292
+ hermes mcp add odoo-assistant \
293
+ --env ODOO_BASE_URL=https://mycompany.odoo.com \
294
+ --env ODOO_API_KEY=your-api-key-here \
295
+ --env ODOO_MCP_MAX_LEVEL=3 \
296
+ --args run odoo-assistant
297
+ ```
298
+
299
+ The examples omit the optional variables. Set `ODOO_DB` when the instance serves several databases, `ODOO_USER` to skip the uid probe, and `ODOO_MCP_MAX_LEVEL` to change the ceiling from its default of `3`.
300
+
301
+ ## License
302
+
303
+ This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
304
+