Labsco
ivnvxd logo

Odoo

β˜… 328

from ivnvxd

Interact with Odoo ERP systems, allowing AI assistants to access and manage business data like contacts, sales, and projects.

πŸ”₯πŸ”₯πŸ”₯πŸ”₯βœ“ VerifiedAccount requiredAdvanced setup

MCP Server for Odoo

CI codecov Ruff Checked with ty Python 3.10+ License: MPL 2.0

An MCP server that enables AI assistants like Claude to interact with Odoo ERP systems. Access business data, search records, create new entries, update existing data, and manage your Odoo instance through natural language.

Works with any Odoo instance! Use YOLO mode for quick testing and demos with any standard Odoo installation. For enterprise security, access controls, and production use, install the Odoo MCP module.

Features

  • πŸ” Search and retrieve any Odoo record (customers, products, invoices, etc.)
  • ✨ Create new records with field validation and permission checks
  • ✏️ Update existing data with smart field handling
  • πŸ—‘οΈ Delete records respecting model-level permissions
  • πŸ”’ Count records matching specific criteria
  • πŸ“‹ Inspect model fields to understand data structure
  • πŸ“Š Server-side aggregation β€” group, sum, and count without pulling raw rows
  • ⚑ Workflow actions β€” invoke any public Odoo method (post invoice, confirm SO, etc.) via an opt-in escape hatch
  • πŸ” Secure access with API key or username/password authentication
  • 🎯 Smart pagination for large datasets
  • 🧠 Smart field selection β€” automatically picks the most relevant fields per model
  • πŸ’¬ LLM-optimized output with hierarchical text formatting
  • 🌍 Multi-language support β€” get responses in your preferred language
  • πŸš€ YOLO Mode for quick access with any Odoo instance (no module required)

Available Tools

search_records

Search for records in any Odoo model with filters.

Copy & paste β€” that's it
{
  "model": "res.partner",
  "domain": [["is_company", "=", true], ["country_id.code", "=", "ES"]],
  "fields": ["name", "email", "phone"],
  "limit": 10
}

Field Selection Options:

  • Omit fields or set to null: Returns smart selection of common fields
  • Specify field list: Returns only those specific fields
  • An empty list [] is treated like null (smart defaults)
  • Use ["__all__"]: Returns all fields (use with caution)

get_record

Retrieve a specific record by ID.

Copy & paste β€” that's it
{
  "model": "res.partner",
  "record_id": 42,
  "fields": ["name", "email", "street", "city"]
}

Field Selection Options:

  • Omit fields or set to null: Returns smart selection of common fields with metadata
  • Specify field list: Returns only those specific fields
  • An empty list [] is treated like null (smart defaults)
  • Use ["__all__"]: Returns all fields without metadata

list_models

List all models enabled for MCP access.

Copy & paste β€” that's it
{}

list_resource_templates

List available resource URI templates and their patterns.

Copy & paste β€” that's it
{}

create_record

Create a new record in Odoo.

Copy & paste β€” that's it
{
  "model": "res.partner",
  "values": {
    "name": "New Customer",
    "email": "customer@example.com",
    "is_company": true
  }
}

update_record

Update an existing record.

Copy & paste β€” that's it
{
  "model": "res.partner",
  "record_id": 42,
  "values": {
    "phone": "+1234567890",
    "website": "https://example.com"
  }
}

delete_record

Delete a record from Odoo.

Copy & paste β€” that's it
{
  "model": "res.partner",
  "record_id": 42
}

post_message

Post a message to a record's chatter (mail.thread). subtype="note" (default) is an internal log; subtype="comment" notifies followers. Set body_is_html=true for HTML markup. Optional partner_ids and attachment_ids reference existing partners and attachments.

Copy & paste β€” that's it
{
  "model": "res.partner",
  "record_id": 42,
  "body": "Called customer, will follow up Tuesday"
}
Copy & paste β€” that's it
{
  "model": "sale.order",
  "record_id": 17,
  "body": "<p>Shipping confirmed for Monday</p>",
  "subtype": "comment",
  "body_is_html": true
}

aggregate_records

Server-side aggregation. Use this whenever the question is "totals/counts/groupings" rather than "list of records" β€” it pushes the work down to PostgreSQL instead of pulling raw rows. Dispatches to formatted_read_group on Odoo 19+ (the new dedicated method) and falls back to read_group with response normalization on older versions. Callers see a consistent response shape on every supported version. When aggregates is omitted, defaults to ["__count"] so each group always carries a count.

Copy & paste β€” that's it
{
  "model": "sale.order",
  "groupby": ["date_order:month"],
  "aggregates": ["amount_total:sum"],
  "domain": [["state", "in", ["sale", "done"]]]
}
Copy & paste β€” that's it
{
  "model": "res.partner",
  "groupby": ["country_id"]
}

call_model_method

Generic XML-RPC execute_kw escape hatch β€” invokes any public method on any model, for workflow actions not covered by CRUD (post invoice, confirm sale order, validate picking, etc.). Available only when both ODOO_YOLO=true (full YOLO) and ODOO_MCP_ENABLE_METHOD_CALLS=true are set; otherwise the tool is not registered. Only public ASCII Python identifiers are accepted as method names β€” dotted, dashed, whitespace, non-ASCII, and _-prefixed names are rejected.

[!WARNING] This tool can invoke any public method, including destructive ones (e.g. unlink, button_draft, custom workflow methods). Enable only in trusted environments where you accept the blast radius. Odoo's record rules and ACLs still apply for the authenticated user.

Copy & paste β€” that's it
{
  "model": "account.move",
  "method": "action_post",
  "arguments": [[42]]
}
Copy & paste β€” that's it
{
  "model": "sale.order",
  "method": "action_confirm",
  "arguments": [[7]],
  "keyword_arguments": {"context": {"lang": "en_US"}}
}

Smart Field Selection

When you omit the fields parameter (or set it to null), the server automatically selects the most relevant fields for each model using a scoring algorithm:

  • Essential fields like id, name, display_name, and active are always included
  • Business-relevant fields (state, amount, email, phone, partner, etc.) are prioritized
  • Technical fields (message threads, activity tracking, website metadata) are excluded
  • Expensive fields (binary, HTML, large text, computed non-stored) are skipped

The default limit is 15 fields per request. Responses include metadata showing which fields were returned and how many total fields are available. You can adjust the limit with ODOO_MCP_MAX_SMART_FIELDS or bypass it entirely with fields: ["__all__"].

Resources

The server also provides direct access to Odoo data through resource URIs:

URI PatternDescription
odoo://{model}/record/{id}Retrieve a specific record by ID
odoo://{model}/searchSearch records with default settings (first 10 records)
odoo://{model}/countCount all records in a model
odoo://{model}/fieldsGet field definitions and metadata for a model

Examples:

  • odoo://res.partner/record/1 β€” Get partner with ID 1
  • odoo://product.product/search β€” List first 10 products
  • odoo://res.partner/count β€” Count all partners
  • odoo://product.product/fields β€” Show all fields for products

Note: Resource URIs don't support query parameters (like ?domain=...). For filtering, pagination, and field selection, use the search_records tool instead.

How It Works

Copy & paste β€” that's it
AI Assistant (Claude, Copilot, etc.)
        ↓ MCP Protocol (stdio or HTTP)
   mcp-server-odoo
        ↓ XML-RPC
   Odoo Instance

The server translates MCP tool calls into Odoo XML-RPC requests. It handles authentication, access control, field selection, data formatting, and error handling β€” presenting Odoo data in an LLM-friendly hierarchical text format.

Security

  • Always use HTTPS in production environments
  • Keep your API keys secure and rotate them regularly
  • Configure model access carefully - only enable necessary models
  • The MCP module respects Odoo's built-in access rights and record rules
  • Each API key is linked to a specific user with their permissions

Development

<details> <summary>Running from source</summary>
Copy & paste β€” that's it
# Clone the repository
git clone https://github.com/ivnvxd/mcp-server-odoo.git
cd mcp-server-odoo

# Install in development mode
pip install -e ".[dev]"

# Run tests
pytest --cov

# Run the server
python -m mcp_server_odoo

# Check version
python -m mcp_server_odoo --version
</details> <details> <summary>Testing with MCP Inspector</summary>
Copy & paste β€” that's it
# Using uvx
npx @modelcontextprotocol/inspector uvx mcp-server-odoo

# Using local installation
npx @modelcontextprotocol/inspector python -m mcp_server_odoo
</details>

Testing

Running Tests

Copy & paste β€” that's it
# Unit tests (no Odoo needed)
uv run pytest -m "not yolo and not mcp" --cov

# YOLO integration tests (vanilla Odoo, no MCP module)
uv run pytest -m "yolo" -v

# MCP integration tests (Odoo + MCP module installed)
uv run pytest -m "mcp" -v

# All tests
uv run pytest --cov

# Run specific test categories
uv run pytest tests/test_tools.py -v
uv run pytest tests/test_server_foundation.py -v

License

This project is licensed under the Mozilla Public License 2.0 (MPL-2.0) - see the LICENSE file for details.

Contributing

Contributions are very welcome! Please see the CONTRIBUTING guide for details.

Support

Thank you for using this project! If you find it helpful and would like to support my work, kindly consider buying me a coffee. Your support is greatly appreciated!

<a href="https://www.buymeacoffee.com/ivnvxd" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" style="height: 60px !important;width: 217px !important;" ></a>

And do not forget to give the project a star if you like it! :star: