An MCP server implementation that integrates with Odoo ERP systems, enabling AI assistants to interact with Odoo data and functionality through the Model Context Protocol.
This project is a fork and modification of the original mcp-odoo by Lê Anh Tuấn, adapted to use the odoorpc library and allow protocol selection.
- Comprehensive Odoo Integration: Full access to Odoo models, records, and methods
- ODOO RPC Communication: Secure connection to Odoo instances via
odoorpc, supporting JSON-RPC and XML-RPC protocols (and their SSL variants). - Flexible Protocol Selection: Specify
jsonrpc,xmlrpc,jsonrpc+ssl, orxmlrpc+sslin the configuration. - Flexible Configuration: Support for config files and environment variables
- Resource Pattern System: URI-based access to Odoo data structures
- Error Handling: Clear error messages for common Odoo API issues
- Stateless Operations: Clean request/response cycle for reliable integration
-
execute
- Execute a custom method on an Odoo model
- Inputs:
model(string): The model name (e.g., 'res.partner')method(string): Method name to executeargs(optional array): Positional argumentskwargs(optional object): Keyword arguments
- Returns: Dictionary with the method result and success indicator
-
search_employee
- Search for employees by name
- Inputs:
name(string): The name (or part of the name) to search forlimit(optional number): The maximum number of results to return (default 20)
- Returns: Object containing success indicator, list of matching employee names and IDs, and any error message
-
search_holidays
- Searches for holidays within a specified date range
- Inputs:
start_date(string): Start date in YYYY-MM-DD formatend_date(string): End date in YYYY-MM-DD formatemployee_id(optional number): Optional employee ID to filter holidays
- Returns: Object containing success indicator, list of holidays found, and any error message
-
odoo://models
- Lists all available models in the Odoo system
- Returns: JSON array of model information
-
odoo://model/{model_name}
- Get information about a specific model including fields
- Example:
odoo://model/res.partner - Returns: JSON object with model metadata and field definitions
-
odoo://record/{model_name}/{record_id}
- Get a specific record by ID
- Example:
odoo://record/res.partner/1 - Returns: JSON object with record data
-
odoo://search/{model_name}/{domain}
- Search for records that match a domain
- Example:
odoo://search/res.partner/[["is_company","=",true]] - Returns: JSON array of matching records (limited to 10 by default)
The Odoo connection parameters for the OdooClient (and thus the MCP Server) are determined through a combination of a JSON configuration file and environment variables.
1. Configuration File:
The system attempts to load connection details from a JSON configuration file. The path to this file is determined as follows:
- Highest precedence: If the
ODOO_CONFIG_FILE_PATH_FROM_CLIenvironment variable is set, its value is used as the path. - Fallback: The
get_odoo_clientfunction accepts aconfig_file_paramargument. If not provided when callingget_odoo_client, it defaults to looking forconfig/odoo_config.json(this path is relative to the project root, e.g.,mcp-odoorpc/config/odoo_config.json). Theodoo_config.json.examplefile in the repository root shows the expected structure.
Example odoo_config.json:
{
"host": "your-odoo-instance-hostname",
"port": 8069,
"db": "your-database-name",
"username": "your-username",
"password": "your-password-or-api-key",
"protocol": "jsonrpc"
}Supported protocols for the "protocol" field are: "jsonrpc", "jsonrpc+ssl", "xmlrpc", and "xmlrpc+ssl".
2. Environment Variables:
Individual Odoo connection parameters can also be set using environment variables. These environment variables take precedence over any values found in the loaded configuration file.
ODOO_HOST: Odoo server hostname or IP address.ODOO_PORT: Odoo server port (must be an integer).ODOO_DB: Database name.ODOO_USERNAME: Login username.ODOO_PASSWORD: Login password or API key.ODOO_PROTOCOL: Connection protocol (e.g.,jsonrpc). If not set and not in the config file,jsonrpcis the default used byOdooClient.
The OdooClient initialization logic (within get_odoo_client) first determines the configuration file path, loads the file if it exists and is valid, and then overrides these settings with any corresponding specific environment variables (ODOO_HOST, ODOO_PORT, etc.) that are set.
Add this to your claude_desktop_config.json:
{
"mcpServers": {
"odoo": {
"command": "python",
"args": [
"-m",
"odoo_mcp_rpc"
],
"env": {
"ODOO_URL": "https://your-odoo-instance.com",
"ODOO_DB": "your-database-name",
"ODOO_USERNAME": "your-username",
"ODOO_PASSWORD": "your-password-or-api-key",
"ODOO_PROTOCOL": "jsonrpc"
}
}
}
}{
"mcpServers": {
"odoo": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"-e",
"ODOO_URL",
"-e",
"ODOO_DB",
"-e",
"ODOO_USERNAME",
"-e",
"ODOO_PASSWORD",
"-e",
"ODOO_PROTOCOL",
"mcp/odoorpc"
],
"env": {
"ODOO_URL": "https://your-odoo-instance.com",
"ODOO_DB": "your-database-name",
"ODOO_USERNAME": "your-username",
"ODOO_PASSWORD": "your-password-or-api-key",
"ODOO_PROTOCOL": "jsonrpc"
}
}
}
}The OdooClient class, located in odoorpc_mcp.odoo_client, provides a direct Python interface for interacting with an Odoo instance. While this client is a core component of the MCP Odoo Server, it can also be used independently in other Python scripts or applications that need to communicate with Odoo.
Initialization:
The get_odoo_client helper function is the recommended way to obtain an initialized OdooClient instance. This function automatically handles loading connection parameters from the configuration file and environment variables, as described in the "Configuration" section.
from odoorpc_mcp.odoo_client import get_odoo_client
# The client will be initialized based on the effective configuration
# (config file + environment variables).
# You can optionally specify a direct path to a config file:
# client = get_odoo_client(config_file_param="custom_config/my_odoo_settings.json")
client = get_odoo_client()
if client:
print(f"Successfully connected to Odoo. UID: {client.uid}")
# You can now use the client instance to interact with Odoo.
# For example, to get the Odoo version (requires odoorpc instance access):
# print(f"Odoo version: {client.odoo.version}")
else:
print("Failed to initialize Odoo client. Please check your configuration and Odoo server accessibility.")Key Methods of OdooClient:
The OdooClient instance provides several methods to interact with Odoo:
-
client.get_models(): Retrieves a list of all available models in the connected Odoo instance, along with their descriptive names.- Returns: A dictionary like
{"model_names": ["res.partner", ...], "models_details": {"res.partner": {"name": "Partner"}, ...}}.
- Returns: A dictionary like
-
client.get_model_info(model_name): Fetches detailed information about a specific model (e.g.,'res.partner').model_name(str): The technical name of the model.- Returns: A dictionary containing model metadata.
-
client.get_model_fields(model_name, attributes=None): Gets the definitions of fields for a given model.model_name(str): The technical name of the model.attributes(list, optional): A list of field attributes to retrieve (e.g.,['string', 'type', 'help', 'relation']). Defaults to a common set of attributes if None.- Returns: A dictionary where keys are field names and values are dictionaries of their attributes.
-
client.search_read(model_name, domain, fields=None, offset=0, limit=None, order=None): Performs a search on a model using an Odoo domain and reads the specified fields from the matching records.model_name(str): The model to search on.domain(list): An Odoo search domain, e.g.,[('is_company', '=', True), ('customer_rank', '>', 0)].fields(list, optional): List of field names to read. Reads a default set if None (behavior might vary by Odoo version or model).offset(int, optional): Number of records to skip.limit(int, optional): Maximum number of records to return.order(str, optional): Order specification string (e.g.,'name ASC').- Returns: A list of dictionaries, where each dictionary represents a record.
-
client.read_records(model_name, ids, fields=None): Reads specific records by their Odoo IDs.model_name(str): The model of the records.ids(list): A list of integer record IDs.fields(list, optional): List of field names to read.- Returns: A list of dictionaries representing the records.
-
client.execute(model_name, method_name, *pos_args, **kw_args): Executes an arbitrary method on the specified Odoo model.model_name(str): The model on which to execute the method.method_name(str): The name of the method to call.*pos_args: Positional arguments to be passed to the Odoo method. For example, if an Odoo method isdef do_something(self, records, name): ..., you would call it asclient.execute('your.model', 'do_something', list_of_record_ids, 'some_name').**kw_args: Keyword arguments to be passed to the Odoo method.- Returns: The result from the Odoo method execution.
Example Usage:
# Assuming 'client' is an initialized OdooClient instance from the previous example.
if client:
try:
# Example 1: Get and print the first 5 models
models_data = client.get_models()
if not models_data.get("error") and models_data.get("model_names"):
print("Available Models (first 5):")
for model_name in models_data["model_names"][:5]:
details = models_data["models_details"].get(model_name, {})
print(f" - {model_name} (Name: {details.get('name', 'N/A')})")
else:
print(f"Could not retrieve models: {models_data.get('error', 'Unknown error')}")
print("\n" + "-"*20 + "\n") # Separator
# Example 2: Search for 'res.partner' records that are companies
print("Searching for company partners (limit 3):")
company_partners = client.search_read(
model_name="res.partner",
domain=[("is_company", "=", True)],
fields=["name", "email", "phone"],
limit=3
)
if company_partners:
for partner in company_partners:
print(f" - Name: {partner.get('name', 'N/A')}, "
f"Email: {partner.get('email', 'N/A')}, "
f"Phone: {partner.get('phone', 'N/A')}")
elif company_partners == []: # Explicitly check for empty list vs None or error
print(" No company partners found matching the criteria.")
else: # Indicates an issue if search_read returned something unexpected (e.g. None or error dict)
print(" Could not perform search or an error occurred.")
except ConnectionError as ce:
print(f"A connection error occurred: {ce}")
except Exception as e:
print(f"An unexpected error occurred during Odoo operations: {e}")Note: This package is not yet available on PyPI. Please use the "From Source Code" method below for now.
pip install odoorpc-mcp-
Clone the repository:
git clone https://github.com/angelmoya/mcp-odoorpc.git cd mcp-odoorpc -
Install the package (use
-efor an editable install if you plan to make changes):pip install . # For an editable install: # pip install -e .
# Using the installed package
odoorpc-mcp
# Using the MCP development tools (from project root)
mcp dev src/odoorpc_mcp/server.py
# With additional dependencies
mcp dev src/odoorpc_mcp/server.py --with pandas --with numpy
# Mount local code for development (from project root)
mcp dev src/odoorpc_mcp/server.py --with-editable .Docker build:
docker build -t mcp/odoorpc:latest -f Dockerfile .When using the MCP tools for Odoo, pay attention to these parameter formatting guidelines:
-
Domain Parameter:
- The following domain formats are supported:
- List format:
[["field", "operator", value], ...] - Object format:
{"conditions": [{"field": "...", "operator": "...", "value": "..."}]} - JSON string of either format
- List format:
- Examples:
- List format:
[["is_company", "=", true]] - Object format:
{"conditions": [{"field": "date_order", "operator": ">=", "value": "2025-03-01"}]} - Multiple conditions:
[["date_order", ">=", "2025-03-01"], ["date_order", "<=", "2025-03-31"]]
- List format:
- The following domain formats are supported:
-
Fields Parameter:
- Should be an array of field names:
["name", "email", "phone"]
- Should be an array of field names: