MCP server for Odoo ERP - connects AI assistants via XML-RPC
Server has 13 tools with good foundation: all are explicitly registered, all have descriptions and input schemas. Naming follows verb_noun convention consistently (search_, read_, create_, update_, delete_, copy_, execute_, call_, export_). However, there are significant gaps in output schema documentation, parameter descriptions lack detail in several tools, and error handling lacks recovery guidance. The domain-specific language (Odoo domain filter syntax) is well-documented in shared constants but parameter descriptions could be more concise and action-oriented for LLM consumption. Tool composition is reasonable (each does one thing), but several parameter descriptions are overly verbose (300+ chars) which wastes tokens. Output schemas are documented in docstrings but not formally specified in the response type hints.
Trigger a workflow action on a record (legacy Odoo method). Returns the workflow result.
Duplicate (copy) an existing record in an Odoo model. Returns the ID of the new record.
Create a new record in an Odoo model. Returns the ID of the newly created record.
Delete one or more records from an Odoo model (unlink). Returns true on success. WARNING: this is destructive and cannot be undone.
Call any ORM or custom method on an Odoo model. Returns the method's result (type depends on the method).
Export search results to a CSV file on the server filesystem. Returns a dict with the output file path.
Output schemas not formally documented in type hints. Docstrings describe outputs (e.g., 'Returns a dict with keys: records, total, limit, offset, model'), but responses lack typed Pydantic models or explicit JSON schema definitions. LLMs cannot reliably plan downstream calls without structured response type information.
Parameter descriptions are verbose and include inline examples that could mislead LLMs. 'domain' parameter description is 467 characters; it repeats examples like [["is_company", "=", true]] which LLMs may copy literally. Should use enums or a formal grammar reference instead of inline examples.
Inferred effective spec: <=2025-11-25.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-22 | D | 59 | <=2025-11-25 | v2 |
| 2026-03-09 | F | 47 | - | v1 |
Export search results to a JSON file on the server filesystem. Returns a dict with the output file path.
Export search results to an Excel (.xlsx) file on the server filesystem. Returns a dict with the output file path.
Get field definitions for an Odoo model (names, types, constraints). Returns metadata about all fields in the model.
Read and aggregate records grouped by fields (e.g., sum by category). Returns a list of groups with aggregated values.
Read a single Odoo record by its ID. Returns the record as a dict with the requested fields. Raises an error if the record does not exist.
Search for records in an Odoo model with domain filtering, field selection, pagination, and sorting. Returns a dict with keys: - records: list of record dicts with the requested fields - total: total count of records matching the domain (ignoring limit/offset) - limit, offset, model: echo of the parameters used
Update (write) fields in one or more existing records. Returns true on success.
Error handling lacks recovery guidance. ToolError exceptions raised in the code (e.g., DomainValidationError, OdooConnectionError, ToolError for readonly mode) do not guide the LLM on what to do next. A domain parsing error should suggest 'Use the format: [["field", "operator", value]]' or 'Call get_model_fields() to see valid field names.'
'execute_method' and 'call_workflow' are ambiguous names that do not clearly distinguish when to use each. 'execute_method' can call any method (action_confirm, action_cancel); 'call_workflow' is deprecated Odoo terminology. Consider renaming to 'execute_action' and marking 'call_workflow' as deprecated/legacy with guidance to use 'execute_method' instead.
Destructive operations (delete_records, update_record) lack confirmation/dry-run patterns. No tool offers a 'dry_run=true' parameter or requires explicit confirmation before execution. An LLM could accidentally delete 500 records with one call.
'record_ids' parameter accepts 'list[integer] | string' (comma-separated). This dual-type design is not idiomatic and requires the LLM to decide between formats. Standardize on 'list[integer]' and parse internally, or use separate tools for single vs batch operations.
'fields' parameter accepts 'string | list[string] | null' (inconsistent types). When string, code calls parse_list_param() to split it. This forces the LLM to guess whether to pass 'name,state' or ['name', 'state']. Standardize on list[string] or provide separate tools.
Export tools (export_records_csv, export_records_json, export_records_xlsx) accept 'output_file' as a parameter, which mixes business logic with system I/O. This is a path traversal risk and couples the tool to the server filesystem. Consider storing exports in a temporary directory and returning a download URL or file handle instead. Current path validation (_validate_output_path) is good but represents a security smell.
'limit' parameter in search_records is capped at 500; export tools cap at 10000. Guidelines recommend capping at 20-50 for LLM context efficiency. Current caps are too high and risk context explosion when an agent calls search_records with limit=500 and the response is large.
No tool offers pagination tokens or cursors (next_cursor). All tools use offset-based pagination, which is inefficient for large datasets and breaks if data is inserted/deleted between calls. Consider adding cursor-based pagination or at least documenting offset limitations.