Introduction
What is the Model Context Protocol (MCP)?
The Model Context Protocol is an open standard that lets AI assistants interact with external tools and data sources through one shared interface.
What is odoo-rust-mcp?
odoo-rust-mcp is a Rust MCP server that connects AI assistants to Odoo ERP. It translates AI requests into Odoo API calls so you can query, create, update, and manage Odoo data through conversational tooling.
Key Capabilities
- 22 tools covering CRUD, workflow actions, reports, model discovery, and cleanup
- 11 built-in prompts covering Odoo data operations plus Owl and frontend guidance
- Multi-instance support for production, staging, and local environments
- Dual authentication for Odoo 19+ JSON-2 and Odoo 18 and earlier JSON-RPC
- Multiple transports including stdio, HTTP, WebSocket, and SSE compatibility
- Built-in Config UI on port 3008
- Hot reload for config, tool, prompt, and instance updates
Who Is This For?
| Audience | Use Case |
|---|---|
| Odoo users and IT admins | Query data, generate reports, and automate workflows through AI assistants |
| Developers | Build AI-powered Odoo integrations and extend the server |
| DevOps engineers | Deploy and operate the MCP server in production |
Technical Details
- Language: Rust
- License: AGPL-3.0
- Version: 0.5.0
- Repository: github.com/milzamsz/odoo-rust-mcp
Config UI at a Glance
The built-in web UI runs at http://localhost:3008.
| Area | Purpose |
|---|---|
| Overview | Runtime summary, auth posture, and config-source checks |
| Instances | Add, edit, test, import, and export Odoo connections |
| Tools | Enable or disable tool groups and individual tools |
| Prompts | Manage built-in and custom prompts |
| Server | Edit server name, instructions, and protocol version |
| Security | Change Config UI password and manage MCP HTTP auth |
| Documentation | Open the built-in docs in a separate tab from the sidebar |
The current Config UI shell with the expanded sidebar and the built-in documentation shortcut.
The sidebar is collapsible, adapts to smaller screens, and now includes a direct route to the documentation.
Documentation Structure
This documentation is organized into two sections:
Functional Documentation
- Getting Started
- Configuration
- Config UI Guide
- Tools Reference
- Prompts Reference
- Use Cases
- Deployment
Developer Documentation
Getting Started
This guide walks you through installing and configuring odoo-rust-mcp for your first use.
Prerequisites
- Odoo Instance: A running Odoo server (v16-19+)
- Credentials: API Key (for v19+) or Username/Password (for v16-18)
- AI Client: Cursor, Claude Desktop, Claude Code, Windsurf, or other MCP-compatible client
Installation Options
Option 1: Pre-built Binary (Recommended)
Download the latest release for your platform:
| Platform | Download |
|---|---|
| Windows x64 | odoo-rust-mcp-x86_64-pc-windows-msvc.zip |
| macOS Intel | odoo-rust-mcp-x86_64-apple-darwin.tar.gz |
| macOS Apple Silicon | odoo-rust-mcp-aarch64-apple-darwin.tar.gz |
| Linux x64 | odoo-rust-mcp-x86_64-unknown-linux-gnu.tar.gz |
Download URL: github.com/rachmataditiya/odoo-rust-mcp/releases/latest
Windows Installation:
# Download and extract
Expand-Archive odoo-rust-mcp-x86_64-pc-windows-msvc.zip -DestinationPath C:\odoo-mcp
cd C:\odoo-mcp
.\install.ps1
Note
The Windows installer automatically sets up two shortcuts on your desktop:
- Odoo MCP Server: Starts the server natively on Windows.
- Odoo WSL MCP Server: Starts the server inside WSL (Ubuntu) in the background.
If you ever need to recreate or refresh these desktop shortcuts, run:
.\install.ps1 -ShortcutThe shortcut creators call the launcher scripts directly with PowerShell
-File, so they behave more consistently when the repository lives on a normal Windows drive or a UNC-backed path.
Linux/macOS Installation:
tar -xzf odoo-rust-mcp-<platform>.tar.gz
cd rust-mcp-<platform>
./install.sh
Option 2: APT (Debian/Ubuntu)
curl -fsSL https://milzamsz.github.io/odoo-rust-mcp/pubkey.gpg | sudo gpg --dearmor -o /usr/share/keyrings/odoo-rust-mcp.gpg
echo "deb [signed-by=/usr/share/keyrings/odoo-rust-mcp.gpg] https://milzamsz.github.io/odoo-rust-mcp stable main" | sudo tee /etc/apt/sources.list.d/odoo-rust-mcp.list
sudo apt update && sudo apt install odoo-rust-mcp
Option 3: Docker
docker run -d --name odoo-mcp \
-e ODOO_URL=https://your-odoo.com \
-e ODOO_DB=mydb \
-e ODOO_API_KEY=your-key \
-p 8787:8787 -p 3008:3008 \
ghcr.io/milzamsz/odoo-rust-mcp:latest
Option 4: Build from Source
See Building from Source for full instructions.
Quick Configuration
Step 1: Create Instance Configuration
Create instances.json with your Odoo connection details:
Odoo 19+ (API Key authentication):
{
"production": {
"url": "https://your-odoo.com",
"db": "production",
"apiKey": "YOUR_API_KEY"
}
}
Odoo 16-18 (Username/Password authentication):
{
"production": {
"url": "https://your-odoo.com",
"db": "production",
"version": "18",
"username": "admin",
"password": "admin"
}
}
Multi-instance (mix and match):
{
"production": {
"url": "https://prod.example.com",
"db": "production",
"apiKey": "prod_api_key"
},
"staging": {
"url": "https://staging.example.com",
"db": "staging",
"version": "18",
"username": "admin",
"password": "admin"
}
}
Step 2: Configure Your AI Client
Cursor (~/.cursor/mcp.json):
{
"mcpServers": {
"odoo": {
"command": "odoo-rust-mcp",
"args": ["--transport", "stdio"],
"env": {
"ODOO_INSTANCES_JSON": "/path/to/instances.json"
}
}
}
}
Claude Desktop (claude_desktop_config.json):
{
"mcpServers": {
"odoo": {
"command": "/path/to/odoo-rust-mcp",
"args": ["--transport", "stdio"],
"env": {
"ODOO_INSTANCES_JSON": "/path/to/instances.json"
}
}
}
}
Claude Code (.mcp.json in project root):
{
"mcpServers": {
"odoo": {
"command": "odoo-rust-mcp",
"args": ["--transport", "stdio"],
"env": {
"ODOO_INSTANCES_JSON": "/path/to/instances.json"
}
}
}
}
Windsurf: Follow Windsurf’s MCP configuration guide. The server configuration is identical – use odoo-rust-mcp --transport stdio as the command.
Verify Installation
Step 1: Validate Configuration
odoo-rust-mcp validate-config
This checks that your instances.json is valid and all required fields are present.
Step 2: Test in Your AI Client
Restart your AI client, then ask:
List available Odoo tools
The assistant should respond with the 24 available tools (e.g., odoo_search, odoo_read, odoo_create, etc.).
Step 3: Run a Simple Query
Search for the first 5 partners in my Odoo instance
If using multi-instance, specify which one:
Search for the first 5 partners in my production instance
CLI Reference
odoo-rust-mcp [OPTIONS] [COMMAND]
Commands:
validate-config Validate Odoo instance configuration
Options:
--transport <MODE> Transport: stdio, http, ws (default: stdio)
--listen <ADDR> Listen address for http/ws (default: 127.0.0.1:8787)
--enable-cleanup-tools Enable destructive cleanup tools
--config-server-port <PORT> Config UI port (default: 3008)
--config-dir <DIR> Config directory override
-h, --help Print help
-V, --version Print version
Connecting MCP Clients to the Desktop App
When running the native Tauri desktop application, the local MCP HTTP server is automatically started in the background on port 8787 (http://127.0.0.1:8787).
You can connect external AI tools (Cursor, Claude Desktop, Claude Code, VS Code, Antigravity, etc.) using one of the following methods.
Option 1: HTTP / SSE Connection (Recommended for Desktop App)
If your client supports HTTP/SSE, you can configure it to connect directly to the running desktop app. This is the most efficient method as it doesn’t spawn additional processes.
1. Claude Code
Add to your project’s .mcp.json file:
{
"mcpServers": {
"odoo-rust-mcp": {
"url": "http://127.0.0.1:8787/mcp"
}
}
}
(If HTTP Authentication is enabled, make sure to add "headers": { "Authorization": "Bearer <YOUR_TOKEN>" } inside the server configuration object)
2. VS Code / Antigravity / Other HTTP-capable clients
Configure your client settings with the server URL:
http://127.0.0.1:8787/mcp (or /sse depending on the client).
Option 2: Stdio Connection (For Cursor and Claude Desktop)
Clients like Cursor and Claude Desktop require launching their own MCP subprocess. For these clients, configure them to run the standalone odoo-rust-mcp.exe binary.
1. Cursor (~/.cursor/mcp.json)
Add the following configuration (replace with the absolute path to your downloaded binary):
{
"mcpServers": {
"odoo-rust-mcp": {
"command": "C:\\path\\to\\odoo-rust-mcp.exe",
"args": ["--transport", "stdio"]
}
}
}
2. Claude Desktop (%APPDATA%\Claude\claude_desktop_config.json)
{
"mcpServers": {
"odoo-rust-mcp": {
"command": "C:\\path\\to\\odoo-rust-mcp.exe",
"args": ["--transport", "stdio"]
}
}
}
Tip
Surfacing ready-to-paste configurations is built into the desktop app! Click Copy MCP Endpoint from the system tray menu, or navigate to the Overview tab in the app to copy tailored configuration snippets.
Next Steps
- Configuration Guide - Advanced configuration, protocol selection, environment variables
- Tools Reference - Complete tool documentation with examples
- Use Cases - Real-world examples and workflows
- Deployment - Docker, Kubernetes, and service deployment
Configuration Guide
This guide covers the runtime configuration surface for odoo-rust-mcp.
Instance Configuration
Multi-Instance Setup (Recommended)
Create instances.json:
{
"production": {
"url": "https://prod.example.com",
"db": "production",
"apiKey": "prod_api_key_here",
"tags": ["prod", "finance"]
},
"staging": {
"url": "https://staging.example.com",
"db": "staging",
"apiKey": "staging_api_key_here"
},
"local": {
"url": "http://localhost:8069",
"db": "localdb",
"version": "18",
"username": "admin",
"password": "admin"
}
}
Instance Fields
| Field | Required | Default | Description |
|---|---|---|---|
url | Yes | - | Odoo server URL |
db | Odoo 18 and earlier | - | Database name |
apiKey | Odoo 19+ | - | API key for JSON-2 authentication |
version | No | - | Odoo version |
username | Odoo 18 and earlier | - | Username for JSON-RPC auth |
password | Odoo 18 and earlier | - | Password for JSON-RPC auth |
protocol | No | auto | auto, jsonrpc, or json2 |
tags | No | [] | Manual labels used by the Config UI |
readOnly | No | false | When true, deny mutating/cleanup/execute tools for this instance even if write env is set. Edit via JSON; Config UI preserves the field on save. |
toolConfig.disabledTools | No | [] | Per-instance tool denylist |
toolConfig.executeAllowlist | No | [] | Required for odoo_execute: [{ "model": "...", "methods": ["..."] }]. Empty denies all execute calls. |
timeout_ms | No | 30000 | Request timeout in milliseconds |
max_retries | No | 2 | Maximum retry attempts |
Protocol Selection
By default, the server auto-detects the protocol based on available credentials:
| Condition | Protocol Used |
|---|---|
apiKey present | JSON-2 API (Odoo 19+) |
username + password + version present | JSON-RPC (Odoo 18 and earlier) |
You can override this with the protocol field when needed.
Single Instance (Legacy)
For simple setups, use environment variables instead of instances.json:
ODOO_URL=https://your-odoo.com
ODOO_DB=mydb
ODOO_API_KEY=your-key
Environment Variables
Core Configuration
| Variable | Default | Description |
|---|---|---|
ODOO_INSTANCES_JSON | - | Path to instances.json |
ODOO_INSTANCES | - | Inline JSON snapshot |
ODOO_URL | - | Single-instance URL fallback |
ODOO_DB | - | Database name |
ODOO_API_KEY | - | API key for Odoo 19+ |
ODOO_VERSION | - | Odoo version |
ODOO_USERNAME | - | Username for Odoo 18 and earlier |
ODOO_PASSWORD | - | Password for Odoo 18 and earlier |
Feature Toggles
| Variable | Default | Description |
|---|---|---|
ODOO_ENABLE_WRITE_TOOLS | false | Enable create, update, delete, workflow, copy tools |
ODOO_ENABLE_EXECUTE_TOOL | false | Enable odoo_execute (still requires a non-empty per-instance executeAllowlist) |
ODOO_ENABLE_CLEANUP_TOOLS | false | Enable cleanup tools only when ODOO_ENABLE_WRITE_TOOLS is also true; cleanup defaults to dry-run |
ODOO_CAPABILITY_CONTROLLED_MODE | false | Hide/reject generic mutations and expose only odoo_execute_capability |
ODOO_CAPABILITY_REGISTRY | — | Required in controlled mode: normalized odoo-agent registry JSON |
ODOO_CAPABILITY_APPROVAL_HMAC_KEY | — | Required in controlled mode: approval-envelope HMAC key of at least 32 bytes |
ODOO_CAPABILITY_STATE_DIR | — | Required in controlled mode: persistent 0700 idempotency-state directory |
ODOO_TIMEOUT_MS | 30000 | Request timeout in milliseconds |
ODOO_MAX_RETRIES | 2 | Retry attempts |
ODOO_MODULE_SNAPSHOT_TTL_SECS | 300 | Installed-module snapshot TTL; 0 refreshes every instance-scoped list |
MCP Configuration
| Variable | Default | Description |
|---|---|---|
MCP_TOOLS_JSON | Auto | Path to tools.json |
MCP_PROMPTS_JSON | Auto | Path to prompts.json |
MCP_SERVER_JSON | Auto | Path to server.json |
Authentication (HTTP Transport)
| Variable | Default | Description |
|---|---|---|
MCP_AUTH_ENABLED | false | Enable bearer-token auth for MCP HTTP |
MCP_AUTH_TOKEN | - | Auth token |
MCP_ALLOWED_ORIGINS | - | Allowed CORS origins |
Config UI
| Variable | Default | Description |
|---|---|---|
ODOO_CONFIG_SERVER_PORT | 3008 | Config UI port |
ODOO_CONFIG_DIR | ~/.config/odoo-rust-mcp | Config directory path |
CONFIG_UI_USERNAME | admin | Login username |
CONFIG_UI_PASSWORD | changeme | Login password |
Logging
| Variable | Default | Description |
|---|---|---|
RUST_LOG | info | Log level |
Transport Modes
stdio
odoo-rust-mcp --transport stdio
- used by local AI clients
- Config UI still runs on port
3008
HTTP
odoo-rust-mcp --transport http --listen 127.0.0.1:8787
- MCP endpoint:
POST /mcp - health endpoint:
GET /health - optional bearer-token auth
WebSocket
odoo-rust-mcp --transport ws --listen 127.0.0.1:8787
Config UI
Access the visual configuration interface at http://localhost:3008.
Main Areas
| Area | Purpose |
|---|---|
| Overview | Runtime summary and posture checks |
| Instances | Add, edit, test, import, and export Odoo connections |
| Tools | Enable or disable tool groups and individual tools |
| Prompts | Edit prompt content and descriptions |
| Server | Edit server name, instructions, protocol version |
| Security | Change Config UI password and manage MCP HTTP auth |
| Documentation | Open the built-in docs in a separate tab |
First-time Setup
- Open
http://localhost:3008 - Sign in with
admin/changeme - Go to Security and change the default password
- Configure instances in Instances
- Optionally enable MCP HTTP auth in Security
- Use the Documentation sidebar entry when you want the built-in docs in a new tab
Hot Reload
Changes made through the Config UI or by directly editing JSON config files take effect immediately.
Configuration File Locations
User Config (Runtime)
| Platform | Directory |
|---|---|
| Linux/macOS | ~/.config/odoo-rust-mcp/ |
| Windows | %APPDATA%\\odoo-rust-mcp\\ or user-specified |
Files: instances.json, tools.json, prompts.json, server.json, env
System Config (Service Installs)
| Platform | Directory |
|---|---|
| Linux (systemd) | /etc/odoo-rust-mcp/ |
| Linux (deb) | /usr/share/odoo-rust-mcp/ |
| Windows | %ProgramData%\\odoo-rust-mcp\\ |
Config Resolution Order
- Explicit environment variable path
- User config directory
- Embedded defaults
Deployment Notes
| Method | Best For |
|---|---|
| Binary + stdio | Local development and single AI client use |
| Binary + HTTP | Remote access and multiple users |
| Docker | Quick isolated deployment |
| Docker Compose | Multi-service setups |
| Kubernetes / Helm | Production deployments |
| systemd / Windows Service | Background service installs |
See Deployment Guide for full setup detail.
Config UI Guide
The Config UI is the built-in control surface for odoo-rust-mcp. It lets you manage instances,
tools, prompts, server metadata, and security settings without editing JSON by hand. It runs on
port 3008 alongside the main MCP server.
URL: http://localhost:3008
Accessing the Config UI
Open a browser and navigate to http://localhost:3008.
Default credentials
Set via environment variables (see Configuration):
CONFIG_UI_USERNAME=admin
CONFIG_UI_PASSWORD=changeme
Important: Change the default password immediately after first install using the Security tab.
Sidebar Navigation
The left sidebar is the main workspace navigator. It is collapsible to save space.
| State | Behavior |
|---|---|
| Expanded | Full width (about 244 px) and shows icon + label for each entry |
| Collapsed | Narrow icon rail (about 64 px) with tooltips on hover |
- Click the sidebar toggle button in the top header to collapse or expand it.
- On screens narrower than 768 px the sidebar becomes a mobile drawer.
- Your preference is saved in
localStorageand restored on the next visit.
Current sidebar entries:
| Section | Entry | Purpose |
|---|---|---|
| Workspace | Overview | Runtime summary and quick posture checks |
| Workspace | Instances | Odoo connection records |
| Workspace | Tools | MCP catalog toggles |
| Workspace | Prompts | Shared prompt definitions |
| Workspace | Documentation | Opens /docs/ in a new tab |
| Operations | Server | Server metadata and runtime source signals |
| Operations | Security | UI password and MCP HTTP auth |
Header and footer
The header stays intentionally quiet and focuses on:
| Item | Description |
|---|---|
| Route title | The current workspace section |
| Sidebar toggle | Collapse or expand the desktop sidebar |
| Keyboard help | Opens the shortcut reference |
| Theme mode | Chooses Light, Dark, or Auto (follow system theme) |
The footer shows:
| Item | Description |
|---|---|
| Hot Reload | Confirms configuration changes apply instantly |
| Unsaved state | Warns when edits are still pending |
The current dark-mode overview with the collapsible sidebar and the Documentation entry in the
workspace navigation.
Tabs Overview
Overview Tab
The authenticated landing route is Overview. It highlights:
- configured instance count
- tool catalog count
- prompt count
- UI auth posture
- runtime source and env snapshot posture
Use this page when you want to quickly confirm whether the running server is reading the config you expect.
Instances Tab
Manage Odoo server connections. This is the most commonly used tab.
Instance list
Each configured instance can be viewed as cards or in a denser table. Use the primary search field to filter by name, URL, database, auth mode, version, or manual tags.
| Field | Description |
|---|---|
| Name | Unique identifier used in tool calls (instance) |
| URL | Odoo server URL |
| Database | Database name |
| Authentication | API Key (Odoo 19+) or Username/Password (Odoo 18 and earlier) |
| Version | Odoo version badge when specified |
| Tags | Optional manual labels such as prod, staging, or finance |
| Status | Connection test result |
| Actions | Test, Edit, Delete |
The current instances workspace supports both card and table views, with public screenshots redacted before capture.
Adding and editing an instance
Click Add Instance or an edit action to open the right-side drawer.
| Field | Required | Notes |
|---|---|---|
url | Yes | Example: https://myodoo.com |
db | Odoo 18 and earlier | Required for JSON-RPC auth |
apiKey | Odoo 19+ | API key from Odoo settings |
version | Optional | Example: 16, 17, 18, 19 |
username | Odoo 18 and earlier | Odoo login username |
password | Odoo 18 and earlier | Odoo login password |
protocol | No | auto, jsonrpc, or json2 |
tags | No | Manual labels for filtering |
timeout_ms | No | Request timeout, default 30000 |
max_retries | No | Retry attempts, default 2 |
Testing connections
- Per-row test runs a connection probe for one instance.
- Test all runs the checks across the visible instance set.
Connection tests run server-side, so they are not blocked by browser CORS restrictions.
Import and export
- Export downloads the current
instances.json. - Import lets you merge or replace the current instance catalog from a JSON file.
Tools Tab
Enable or disable MCP tools. Disabled tools disappear from AI clients entirely.
The Tools tab also compares the live runtime catalog with the packaged default catalog. If an
upgrade ships new tools that are missing from your existing tools.json, the catalog drift panel
lists them and lets you import only those missing packaged tools. Existing runtime tool definitions
and local guard edits are preserved.
Tools are organized into three operation groups:
| Group | Gate | Purpose |
|---|---|---|
| Read Operations | None | Always-available read and discovery tools |
| Write Operations | ODOO_ENABLE_WRITE_TOOLS=true | Create, update, delete, workflow, copy |
| Execute | ODOO_ENABLE_EXECUTE_TOOL=true plus non-empty toolConfig.executeAllowlist | odoo_execute only |
| Cleanup Operations | ODOO_ENABLE_CLEANUP_TOOLS=true | Cleanup and deep-cleanup tools |
Instance readOnly and executeAllowlist are JSON-managed. Saving an instance in the Config UI preserves those fields; it does not provide editors for them in this release.
Each group exposes bulk enable and disable controls, plus individual toggles.
The current tools workspace keeps the catalog compact while preserving grouped enable and disable controls.
Prompts Tab
Manage MCP prompts that AI clients can request by name. The prompt drawer follows the same right-side editing pattern as instance editing, so long prompt content remains usable on smaller screens.
The prompt workspace keeps the shared prompt catalog in the same shell as instances, tools, and server settings.
Server Tab
Edit the MCP server identity and inspect runtime source signals that explain where live instance data is coming from.
Key uses:
- update server name
- update instructions exposed to MCP clients
- inspect env snapshot posture
- review alternate nearby
instances.jsonfiles that may confuse operators
The current server workspace combines editable metadata with runtime source visibility.
Security Tab
Manage authentication for both the Config UI and the MCP HTTP transport.
The Security tab covers Config UI password changes and MCP HTTP authentication token management in the current shell.
Config UI password
Change the password for the web interface while signed in.
MCP HTTP auth
When running in HTTP transport mode, you can require AI clients to present a bearer token:
- Enable MCP auth.
- Generate a token.
- Copy the token into your AI client’s MCP configuration.
The token is written to the env file and hot-reloaded immediately.
Hot-Reload Behavior
All changes through the Config UI are applied instantly without restarting the server:
| Change | Effect |
|---|---|
| Save instances | OdooClientPool reloads and cached clients clear |
| Save tools or prompts | Registry reloads |
| Save server config | Server name and instructions update |
| Change password | UI auth reloads in memory |
| Toggle MCP auth | HTTP transport auth reloads |
Keyboard Shortcuts
| Action | Shortcut |
|---|---|
| Toggle sidebar | Ctrl/Cmd + B |
| Open create flow | Ctrl/Cmd + N |
| Focus primary search | / |
| Jump to Overview | Ctrl/Cmd + 1 |
| Jump to Instances | Ctrl/Cmd + 2 |
| Jump to Tools | Ctrl/Cmd + 3 |
| Jump to Prompts | Ctrl/Cmd + 4 |
| Jump to Server | Ctrl/Cmd + 5 |
| Jump to Security | Ctrl/Cmd + 6 |
| Open shortcuts help | ? |
Accessing the Documentation
The built-in mdBook documentation is served at http://localhost:3008/docs/ when the docs have
been built. Official release packages include the generated book, so installed copies expose this
route without a separate mdBook build.
You can also open it from the Documentation sidebar item, which launches the docs in a new tab so the current Config UI workspace is not interrupted.
The Rust Hexagon mark is shared by the Config UI header, browser favicon, Windows shortcut, and this documentation site.
Tools Reference
Complete reference for all 24 tools available in odoo-rust-mcp.
Read Operations (Always Available)
odoo_search
Search for records matching domain filters. Returns IDs only.
{
"instance": "production",
"model": "res.partner",
"domain": [["is_company", "=", true]],
"limit": 10,
"offset": 0,
"order": "name ASC"
}
Response:
{ "ids": [1, 2, 3], "count": 3 }
odoo_search_read
Search and read records in one operation. Returns full record data.
{
"instance": "production",
"model": "res.partner",
"domain": [["is_company", "=", true]],
"fields": ["name", "email", "phone"],
"limit": 10,
"order": "name ASC"
}
Response:
{
"records": [
{"id": 1, "name": "Acme Corp", "email": "info@acme.com", "phone": "+1234567890"}
],
"count": 1
}
odoo_read
Read specific records by IDs.
{
"instance": "production",
"model": "res.partner",
"ids": [1, 2, 3],
"fields": ["name", "email"]
}
odoo_count
Count records matching domain.
{
"instance": "production",
"model": "sale.order",
"domain": [["state", "=", "sale"]]
}
Response:
{ "count": 42 }
odoo_read_group
Aggregate records with GROUP BY.
{
"instance": "production",
"model": "sale.order",
"domain": [["state", "=", "sale"]],
"fields": ["amount_total:sum"],
"groupby": ["partner_id"]
}
odoo_name_search
Autocomplete-style name search.
{
"instance": "production",
"model": "res.partner",
"name": "Acme",
"limit": 10
}
odoo_name_get
Get display names for record IDs.
{
"instance": "production",
"model": "res.partner",
"ids": [1, 2, 3]
}
odoo_default_get
Get default values for new record creation.
{
"instance": "production",
"model": "sale.order",
"fields": ["partner_id", "date_order"]
}
odoo_list_models
List available Odoo models.
{
"instance": "production",
"domain": [["transient", "=", false]],
"limit": 50
}
odoo_get_model_metadata
Get field definitions and types for a model.
{
"instance": "production",
"model": "sale.order"
}
odoo_check_access
Check user permissions on a model.
{
"instance": "production",
"model": "res.partner",
"operation": "write",
"ids": [1, 2, 3]
}
odoo_generate_report
Generate PDF report (returns base64).
{
"instance": "production",
"reportName": "sale.report_saleorder",
"ids": [42]
}
odoo_onchange
Simulate form onchange behavior.
{
"instance": "production",
"model": "sale.order",
"ids": [],
"values": {"partner_id": 42}
}
Write Operations
Requires:
ODOO_ENABLE_WRITE_TOOLS=true
odoo_create
Create a new record.
{
"instance": "production",
"model": "res.partner",
"values": {
"name": "New Customer",
"email": "customer@example.com"
}
}
Response:
{ "id": 123, "success": true }
odoo_create_batch
Create multiple records (max 100).
{
"instance": "production",
"model": "res.partner",
"values": [
{"name": "Partner 1", "email": "p1@example.com"},
{"name": "Partner 2", "email": "p2@example.com"}
]
}
odoo_update
Update existing records.
{
"instance": "production",
"model": "res.partner",
"ids": [123],
"values": {
"email": "updated@example.com"
}
}
odoo_delete
Delete records.
{
"instance": "production",
"model": "res.partner",
"ids": [123]
}
odoo_copy
Duplicate a record.
{
"instance": "production",
"model": "sale.order",
"id": 42,
"default": {"name": "Copy of SO042"}
}
odoo_execute
Execute arbitrary model method.
{
"instance": "production",
"model": "sale.order",
"method": "action_confirm",
"args": [[42]]
}
odoo_workflow_action
Execute workflow action button.
{
"instance": "production",
"model": "sale.order",
"ids": [42],
"action": "action_confirm"
}
Cleanup Operations
Requires:
ODOO_ENABLE_CLEANUP_TOOLS=true⚠️ Use with caution!
odoo_database_cleanup
Comprehensive database cleanup.
{
"instance": "staging",
"removeTestData": true,
"cleanupDrafts": true,
"dryRun": true
}
odoo_deep_cleanup
DESTRUCTIVE: Remove all non-essential data.
{
"instance": "staging",
"dryRun": true,
"keepCompanyDefaults": true,
"keepUserAccounts": true
}
Domain Filter Syntax
# Basic
["name", "=", "John"]
["age", ">", 18]
["name", "ilike", "john"]
# List
["state", "in", ["draft", "posted"]]
# Logical (Polish notation)
["&", ("a", "=", 1), ("b", "=", 2)] # AND
["|", ("a", "=", 1), ("a", "=", 2)] # OR
["!", ("state", "=", "cancel")] # NOT
# Related fields
["partner_id.country_id.code", "=", "US"]
Common Models
| Model | Description |
|---|---|
res.partner | Contacts/Customers |
sale.order | Sales Orders |
purchase.order | Purchase Orders |
account.move | Invoices/Bills |
stock.picking | Transfers |
product.product | Products |
hr.employee | Employees |
project.task | Tasks |
Prompts Reference
Built-in prompts provide context and guidance to AI assistants when working with Odoo data, workflows, and addon development.
Prompts are defined in prompts.json and support hot-reload – changes take effect immediately without restarting the server.
Available Prompts
odoo_common_models
Description: List of commonly used Odoo models across different modules.
Contents:
- Sales & CRM:
sale.order,crm.lead,crm.team - Accounting:
account.move,account.payment,account.journal - Inventory:
stock.picking,stock.move,stock.warehouse,stock.quant - Products:
product.product,product.template,product.category - Partners:
res.partner,res.company,res.users - HR:
hr.employee,hr.department,hr.leave - Projects:
project.project,project.task - Purchase:
purchase.order - POS:
pos.order,pos.session,pos.config
odoo_domain_filters
Description: Complete guide for Odoo domain filter syntax.
Covers:
- Basic operators:
=,!=,>,>=,<,<= - String operators:
like,ilike,=like,=ilike - List operators:
in,not in - Logical operators:
&(AND),|(OR),!(NOT) - Related field traversal:
partner_id.country_id.code - Complex examples
odoo_field_types
Description: Odoo field types and relational fields explained.
Covers:
- Basic fields: Char, Text, Integer, Float, Monetary, Boolean, Date, Datetime
- Selection fields
- Binary and Html fields
- Relational fields:
- Many2one (N:1)
- One2many (1:N)
- Many2many (N:N)
- Computed and related fields
- Naming conventions (
_id,_ids,_count)
odoo_workflow_states
Description: Common workflow states for Odoo documents.
Documents covered:
| Document | States |
|---|---|
| Sale Order | draft → sent → sale → done / cancel |
| Purchase Order | draft → sent → to approve → purchase → done |
| Invoice | draft → posted → cancel |
| Stock Picking | draft → waiting → confirmed → assigned → done |
| CRM Lead | lead / opportunity (type) |
| POS Order | draft → paid → done → invoiced |
odoo_read_group
Description: How to use read_group for aggregation and reporting.
Covers:
- Syntax and parameters
- Aggregation functions:
sum,count,avg,min,max - Time grouping:
day,week,month,quarter,year - Practical examples:
- Count orders by state
- Sum sales by customer
- Monthly revenue analysis
odoo_context
Description: Odoo context parameters and their usage.
Covers:
- Session keys:
uid,lang,tz,allowed_company_ids - Active record keys:
active_id,active_ids,active_model - Behavior modifiers:
default_*,search_default_* - Skip validations:
tracking_disable,mail_create_nosubscribe - Import mode
odoo_api_tips
Description: Best practices for Odoo API usage.
Topics:
- Performance tips:
- Limit fields in
search_read - Use pagination
- Use
read_groupfor aggregation - Avoid search in loops
- Limit fields in
- Field selection tips
- Common patterns
- Error handling
odoo_owl_components
Description: Owl component structure and debugging patterns for Odoo addons.
Covers:
- Standard
static/srcJS/XML/SCSS component layout /** @odoo-module **/usagesetup()over constructors- Template naming with
addon_name.ComponentName - When to use
useService() - First-pass debugging checks for assets, template names, imports, and mounting
odoo_assets_and_bundles
Description: Asset bundle choices and module wiring for Odoo frontend code.
Covers:
web.assets_backendweb.assets_frontendweb.assets_unit_tests- Manifest
assetswiring for JS/XML/SCSS files @web/...imports vs addon-local relative imports- Bundle-loading and module-header debugging checks
odoo_frontend_contexts
Description: How to choose between backend, client action, standalone Owl, and website contexts.
Covers:
- Existing backend screen extensions
- Client actions for navigable backend features
- Standalone Owl apps with their own mount target
- Portal and website runtime separation
- Practical rules for staying close to the addon’s existing structure
odoo_qweb_and_templates
Description: QWeb and Owl template rules, directives, and version-sensitive notes.
Covers:
- XML templates for production components
xml:space="preserve"and template naming alignmentt-if,t-elif,t-foreach,t-key, and event bindings- Dynamic attributes with
t-att-*andt-attf-* - Odoo 18
t-escvs Odoo 19t-outguidance - Common template mismatches and naming mistakes
Using Prompts
In your AI client, you can reference prompts to get context:
Cursor/Claude:
Show me the odoo_domain_filters prompt, then help me write a domain
to find all unpaid invoices from this year.
Programmatic access:
{
"method": "prompts/get",
"params": {
"name": "odoo_common_models"
}
}
Use Cases & Examples
Real-world examples of using odoo-rust-mcp with AI assistants.
Sales & CRM
Find Top Customers by Revenue
Search for sale orders with state 'sale' from this year,
group by partner and sum the amount_total.
Show me the top 10 customers.
Tool used: odoo_read_group
Create a Quotation
Create a new sale order for partner ID 42 with 3 lines:
- Product ID 10, qty 5
- Product ID 15, qty 2
- Product ID 20, qty 10
Tools used: odoo_create (sale.order), odoo_create_batch (sale.order.line)
Confirm Pending Orders
Find all draft sale orders older than 7 days and confirm them.
Tools used: odoo_search, odoo_workflow_action (action_confirm)
Inventory
Check Stock Levels
List all products where qty_available is less than 10.
Include product name, quantity on hand, and category.
Tool used: odoo_search_read on product.product
Process Pending Deliveries
Find all stock pickings in 'assigned' state for today and validate them.
Tools used: odoo_search, odoo_workflow_action (button_validate)
Accounting
Unpaid Invoice Analysis
Show me all unpaid customer invoices grouped by partner,
with total amount and count.
Tool used: odoo_read_group on account.move
{
"instance": "production",
"model": "account.move",
"domain": [
["state", "=", "posted"],
["move_type", "=", "out_invoice"],
["payment_state", "!=", "paid"]
],
"fields": ["amount_residual:sum", "id:count"],
"groupby": ["partner_id"]
}
Post Draft Invoices
Find all draft invoices for this month and post them.
Tools used: odoo_search, odoo_workflow_action (action_post)
Multi-Instance Workflow
Compare Data Across Environments
Count the number of active products in both production and staging instances.
Tools used: odoo_count (called twice with different instance values)
// First call
{"instance": "production", "model": "product.product", "domain": [["active", "=", true]]}
// Second call
{"instance": "staging", "model": "product.product", "domain": [["active", "=", true]]}
Verify Staging Before Go-Live
Compare the number of sale orders in staging vs production
to make sure test data won't leak.
Tools used: odoo_count on both instances, odoo_search_read to inspect differences
HR & Employees
Employee Directory Lookup
Search for all employees in the Sales department and show
their name, job title, and work email.
Tool used: odoo_search_read on hr.employee
{
"instance": "production",
"model": "hr.employee",
"domain": [["department_id.name", "=", "Sales"]],
"fields": ["name", "job_title", "work_email"],
"limit": 50
}
Leave Balance Check
Show me all approved leaves for this month with employee name and leave type.
Tool used: odoo_search_read on hr.leave
Contacts
Bulk Update Partners
Find all partners in category 'Prospects' and add them to the
'Newsletter' mailing list.
Tools used: odoo_search, odoo_update
Find Duplicate Contacts
Search for partners with duplicate email addresses.
Tool used: odoo_search_read with odoo_read_group
Reports
Generate Sales Report
Generate a PDF sales report for order ID 42.
Tool used: odoo_generate_report
{
"instance": "production",
"reportName": "sale.report_saleorder",
"ids": [42]
}
Monthly Revenue Dashboard
Show me total revenue by month for the current year.
Tool used: odoo_read_group
{
"instance": "production",
"model": "account.move",
"domain": [
["state", "=", "posted"],
["move_type", "=", "out_invoice"],
["invoice_date", ">=", "2026-01-01"]
],
"fields": ["amount_untaxed_signed:sum"],
"groupby": ["invoice_date:month"]
}
Model Discovery
Explore Available Models
List all non-transient models in my Odoo instance.
Tool used: odoo_list_models
Understand a Model
Show me the fields and their types for the sale.order model.
Tool used: odoo_get_model_metadata
Chained Discovery Workflow
A typical model discovery workflow chains multiple tools:
-
List models to find relevant ones:
List models matching "stock" in my production instance. -
Get metadata for the model of interest:
Show me the fields for stock.picking. -
Search records to see real data:
Show me 5 recent stock pickings with their state, partner, and scheduled date. -
Understand defaults for creating new records:
Get default values for a new stock.picking.
Tools used: odoo_list_models -> odoo_get_model_metadata -> odoo_search_read -> odoo_default_get
Best Practices
1. Always specify fields
# Good - only requested fields
"fields": ["name", "email", "phone"]
# Avoid - fetches everything
"fields": []
2. Use pagination for large datasets
{
"limit": 100,
"offset": 0
}
3. Check access before writing
Before updating those records, check if I have write access.
Tool used: odoo_check_access
4. Use dry run for cleanup
Run a dry run of database cleanup first to see what would be affected.
{
"dryRun": true
}
5. Specify the instance name
When using multi-instance setups, always specify which instance to target:
Search for partners in my **staging** instance.
If omitted, the server will use the first/default instance.
Deployment
How to deploy odoo-rust-mcp in production environments.
Deployment Options
| Method | Best For |
|---|---|
| Direct binary | Simple setups, development |
| Install script | Linux/macOS with systemd/launchd |
| Docker | Single-server production |
| Docker Compose | Multi-service stacks (n8n, Dify) |
| Kubernetes | Cluster deployments |
| Helm | Templated Kubernetes deployments |
Direct Binary
Download a pre-built binary from GitHub Releases or build from source.
# stdio transport (for AI clients like Cursor, Claude Desktop)
./odoo-rust-mcp --transport stdio
# HTTP transport (for remote access + Config UI)
./odoo-rust-mcp --transport http --listen 127.0.0.1:8787
# WebSocket transport
./odoo-rust-mcp --transport ws --listen 127.0.0.1:8787
When using HTTP or WebSocket transport, the Config UI is available at http://localhost:3008.
Install Script
The included install.sh script handles binary installation and service setup:
# Download and extract a release, then:
./install.sh # Install binary + config files
./install.sh service # Install + start as background service
./install.sh uninstall # Remove everything
The script auto-detects the OS and installs the appropriate service (systemd on Linux, launchd on macOS).
Custom Install Prefix
# Install to user-local directory (no sudo needed)
PREFIX=$HOME/.local ./install.sh
Linux (systemd)
Using the Install Script
./install.sh service
This creates:
- Binary at
/usr/local/bin/odoo-rust-mcp - Config at
/usr/local/share/odoo-rust-mcp/ - Environment file at
/usr/local/etc/odoo-rust-mcp.env - Systemd unit at
/etc/systemd/system/odoo-rust-mcp.service
Manual systemd Setup
Create /etc/systemd/system/odoo-rust-mcp.service:
[Unit]
Description=Odoo Rust MCP Server
After=network.target
[Service]
Type=simple
User=nobody
Group=nogroup
EnvironmentFile=/usr/local/etc/odoo-rust-mcp.env
ExecStart=/usr/local/bin/odoo-rust-mcp --transport http --listen 127.0.0.1:8787
Restart=on-failure
RestartSec=5
# Security hardening
NoNewPrivileges=true
ProtectSystem=full
PrivateTmp=true
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable odoo-rust-mcp
sudo systemctl start odoo-rust-mcp
# Check status
sudo systemctl status odoo-rust-mcp
# View logs
sudo journalctl -u odoo-rust-mcp -f
macOS (launchd)
Using the Install Script
./install.sh service
This creates:
- Binary at
/usr/local/bin/odoo-rust-mcp - Config at
~/.config/odoo-rust-mcp/ - Plist at
~/Library/LaunchAgents/com.odoo.rust-mcp.plist
Managing the Service
# Start
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.odoo.rust-mcp.plist
# Stop
launchctl bootout gui/$(id -u)/com.odoo.rust-mcp
# Status
launchctl print gui/$(id -u)/com.odoo.rust-mcp
# Logs
tail -f ~/.config/odoo-rust-mcp/stdout.log
Windows
PowerShell Install Script
# Run as Administrator
.\scripts\install.ps1
Manual Setup
- Download the Windows binary from GitHub Releases
- Place
odoo-rust-mcp.exein a permanent location (e.g.,%LOCALAPPDATA%\odoo-rust-mcp\) - Copy
static/dist/alongside the binary for Config UI - Add the directory to your
PATH - Create
~/.config/odoo-rust-mcp/instances.jsonwith your Odoo credentials
Running as a Background Process
# Start in background
Start-Process -NoNewWindow odoo-rust-mcp -ArgumentList "--transport","http","--listen","127.0.0.1:8787"
For a persistent Windows service, use NSSM or Task Scheduler.
Docker
Single Container
docker build -f rust-mcp/Dockerfile -t odoo-rust-mcp:latest .
docker run -d \
--name odoo-mcp \
-p 8787:8787 \
-p 3008:3008 \
-e ODOO_URL=http://host.docker.internal:8069 \
-e ODOO_DB=mydb \
-e ODOO_API_KEY=your-key \
-e CONFIG_UI_USERNAME=admin \
-e CONFIG_UI_PASSWORD=changeme \
odoo-rust-mcp:latest
Container Details
| Feature | Value |
|---|---|
| Base image | debian:bookworm-slim |
| User | mcp (non-root) |
| MCP port | 8787 |
| Config UI port | 3008 |
| Config path | /config/ |
| Health check | POST /mcp (ping) |
| Default transport | HTTP on 0.0.0.0:8787 |
Docker Compose
Basic Setup
# Create .env from example
cp dotenv.example .env
# Edit .env with your credentials
# Build and run
docker compose up -d
Multi-Instance Configuration
Create instances.json in the project root:
{
"production": {
"url": "http://host.docker.internal:8069",
"db": "production",
"apiKey": "your-api-key"
},
"staging": {
"url": "http://staging.example.com:8069",
"db": "staging",
"version": "18",
"username": "admin",
"password": "admin"
}
}
The docker-compose.yml mounts this file automatically.
Integration with Other Containers
The compose file creates an mcp-network bridge network. Other containers (n8n, Dify, etc.) can connect to the MCP server:
# In another docker-compose.yml
services:
n8n:
networks:
- mcp-network
environment:
MCP_URL: http://odoo-mcp:8787/mcp
networks:
mcp-network:
external: true
Traefik Reverse Proxy
The compose file includes Traefik labels:
labels:
- "traefik.enable=true"
- "traefik.http.routers.odoo-mcp.rule=Host(`mcp.localhost`)"
- "traefik.http.services.odoo-mcp.loadbalancer.server.port=8787"
- "traefik.http.routers.odoo-mcp-config.rule=Host(`mcp-config.localhost`)"
- "traefik.http.services.odoo-mcp-config.loadbalancer.server.port=3008"
Resource Limits
Default limits in the compose file:
| Resource | Limit | Reservation |
|---|---|---|
| CPU | 1 core | 0.25 cores |
| Memory | 256 MB | 64 MB |
Kubernetes
The k8s/ directory contains 7 manifests managed by Kustomize:
k8s/
+-- kustomization.yaml # Kustomize configuration
+-- namespace.yaml # odoo-mcp namespace
+-- configmap.yaml # tools.json, prompts.json, server.json, instances.json
+-- secret.yaml # API keys, auth tokens, Config UI credentials
+-- deployment.yaml # 2 replicas, probes, anti-affinity, security context
+-- service.yaml # ClusterIP with ports 8787 + 3008
+-- ingress.yaml # nginx ingress with TLS (optional)
Quick Deploy
# Apply all manifests
kubectl apply -k k8s/
# Check status
kubectl get pods -n odoo-mcp
kubectl logs -f deployment/odoo-mcp -n odoo-mcp
Customization with Overlays
# Create a production overlay
mkdir -p k8s/overlays/production
cat > k8s/overlays/production/kustomization.yaml <<EOF
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../
patches:
- patch: |-
- op: replace
path: /spec/replicas
value: 3
target:
kind: Deployment
name: odoo-mcp
EOF
kubectl apply -k k8s/overlays/production
Key Deployment Features
- Non-root execution:
runAsUser: 1000,readOnlyRootFilesystem: true - Pod anti-affinity: Spreads replicas across nodes
- Three-probe health checks: startup, liveness, readiness
- Resource limits: 100m-500m CPU, 64Mi-256Mi memory
- Config via ConfigMap: Hot-reloadable tool/prompt definitions
- Secrets from Secret: API keys, auth tokens via
secretKeyRef
Helm Chart
The helm/odoo-rust-mcp/ directory contains a full Helm chart.
Install
helm install odoo-mcp helm/odoo-rust-mcp/ \
--set odoo.url=http://odoo-service:8069 \
--set odoo.db=production \
--set odoo.apiKey=your-api-key
Multi-Instance Install
helm install odoo-mcp helm/odoo-rust-mcp/ \
-f my-values.yaml
Where my-values.yaml contains:
odooInstances:
json: |
{
"production": {
"url": "http://odoo-service:8069",
"db": "production",
"apiKey": "your-production-key"
},
"staging": {
"url": "http://odoo-staging:8069",
"db": "staging",
"apiKey": "your-staging-key"
}
}
mcp:
auth:
enabled: true
token: "your-secure-token"
configServer:
enabled: true
auth:
username: admin
password: "strong-password"
ingress:
enabled: true
className: nginx
hosts:
- host: mcp.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: odoo-mcp-tls
hosts:
- mcp.example.com
Key Helm Values
| Value | Default | Description |
|---|---|---|
replicaCount | 2 | Number of replicas |
image.repository | ghcr.io/milzamsz/odoo-rust-mcp | Container image |
odooInstances.json | (example) | Multi-instance JSON config |
mcp.auth.enabled | false | Enable HTTP auth |
mcp.auth.token | “” | Bearer token |
configServer.enabled | true | Enable Config UI |
configServer.port | 3008 | Config UI port |
autoscaling.enabled | false | Enable HPA |
autoscaling.maxReplicas | 10 | Max replicas |
ingress.enabled | false | Enable ingress |
Autoscaling
autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 10
targetCPUUtilizationPercentage: 80
Production Checklist
Security
- Change default Config UI credentials (
CONFIG_UI_USERNAME,CONFIG_UI_PASSWORD) - Enable MCP HTTP auth (
MCP_AUTH_ENABLED=true) and set a strong token - Use HTTPS via reverse proxy (Traefik, nginx, etc.)
- Bind to
127.0.0.1if only local access is needed - Run as non-root user
- Use read-only root filesystem (Docker/K8s)
Performance
- Set appropriate
ODOO_TIMEOUT_MS(default: 30000ms) - Set
ODOO_MAX_RETRIESfor unreliable networks - Configure resource limits (CPU/memory)
- Use
RUST_LOG=infoin production (notdebug)
Monitoring
- Configure health check probes
- Monitor
GET /health(MCP server) andGET /health(Config UI) - Set up log aggregation (
RUST_LOG=infooutputs to stdout/stderr)
Configuration
- Use
instances.jsonfile for multi-instance setups (not inline env vars) - Mount config files as read-only volumes
- Use secrets management for API keys (K8s Secrets, Docker secrets, vault)
Building from Source
Guide to building odoo-rust-mcp from source code.
Prerequisites
- Rust toolchain: 1.85+ (for Rust 2024 edition) – install via rustup
- Node.js: 20+ with npm
- Git: For cloning the repository
Build Order
The React Config UI must be built before the Rust binary, because the built UI assets are embedded into the binary via include_dir!.
1. config-ui (npm ci && npm run build)
|
v
rust-mcp/static/dist/ (generated)
|
v
2. odoo-rust-mcp (cargo build --release)
|
v
rust-mcp/target/release/odoo-rust-mcp (final binary)
Quick Build
# Clone the repository
git clone https://github.com/rachmataditiya/odoo-rust-mcp.git
cd odoo-rust-mcp
# Step 1: Build React UI
cd config-ui
npm ci
npm run build
cd ..
# Step 2: Build Rust binary
cd rust-mcp
cargo build --release
Binary location:
- Debug:
rust-mcp/target/debug/odoo-rust-mcp - Release:
rust-mcp/target/release/odoo-rust-mcp
Windows (PowerShell):
git clone https://github.com/rachmataditiya/odoo-rust-mcp.git
cd odoo-rust-mcp
# Step 1: Build React UI
cd config-ui
npm ci
npm run build
cd ..
# Step 2: Build Rust binary
cd rust-mcp
cargo build --release
Development Setup
1. Install Rust
# Linux/macOS
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Windows: Download and run rustup-init.exe from https://rustup.rs/
Verify: rustc --version (must be 1.85+)
2. Install Node.js
Use nvm or download from nodejs.org:
nvm install 20
nvm use 20
Verify: node --version (must be 20+)
3. Clone and Build
git clone https://github.com/rachmataditiya/odoo-rust-mcp.git
cd odoo-rust-mcp
# Build config UI first
cd config-ui && npm ci && npm run build && cd ..
# Build Rust server
cd rust-mcp && cargo build && cd ..
4. Configure Environment
# Create config directory
mkdir -p ~/.config/odoo-rust-mcp
# Create instances.json
cat > ~/.config/odoo-rust-mcp/instances.json <<EOF
{
"local": {
"url": "http://localhost:8069",
"db": "mydb",
"version": "18",
"username": "admin",
"password": "admin"
}
}
EOF
5. Run the Server
# stdio transport (for AI clients)
./rust-mcp/target/debug/odoo-rust-mcp --transport stdio
# HTTP transport (with Config UI on :3008)
./rust-mcp/target/debug/odoo-rust-mcp --transport http --listen 127.0.0.1:8787
Config UI Development
The Config UI is a React 18 + TypeScript + Tailwind CSS app in config-ui/.
cd config-ui
# Install dependencies
npm ci
# Development server (HMR on :5173)
npm run dev
# Production build (outputs to ../rust-mcp/static/dist)
npm run build
# Type checking
npm run typecheck
# Linting
npm run lint
# Run tests
npm test
# Tests with coverage
npm run test:coverage
During development, run the Rust server in one terminal and the Vite dev server in another:
# Terminal 1: Rust server
cd rust-mcp && cargo run -- --transport http --listen 127.0.0.1:8787
# Terminal 2: Vite dev server with HMR
cd config-ui && npm run dev
# Access UI at http://localhost:5173
Build Options
Debug Build (Fast compilation)
cd rust-mcp && cargo build
Release Build (Optimized, stripped)
cd rust-mcp && cargo build --release
Cross-Compilation
Using cross:
cargo install cross
# Build for Linux (from macOS/Windows)
cross build --release --target x86_64-unknown-linux-gnu
# Build for Windows (from Linux/macOS)
cross build --release --target x86_64-pc-windows-msvc
Linting & Formatting
cd rust-mcp
# Format code (required before commit)
cargo fmt
# Check formatting (CI mode)
cargo fmt --check
# Run clippy linter (must pass with zero warnings)
cargo clippy -- -D warnings
CI will fail if cargo fmt or cargo clippy produce any output.
Docker Build
The Dockerfile is at rust-mcp/Dockerfile and uses a multi-stage build:
- Builder stage: Installs Node.js 20, builds React UI, then builds Rust binary
- Runtime stage: Debian slim with just the binary and static assets
# From repository root
docker build -f rust-mcp/Dockerfile -t odoo-rust-mcp:latest .
# Run container
docker run -d \
-e ODOO_URL=http://host.docker.internal:8069 \
-e ODOO_DB=mydb \
-e ODOO_API_KEY=your-key \
-p 8787:8787 -p 3008:3008 \
odoo-rust-mcp:latest
Docker Compose
# Create .env from example
cp dotenv.example .env
# Edit .env with your credentials
# Build and run
docker compose up --build
Release Process
./scripts/release.sh 0.5.0
This script:
- Bumps version in
rust-mcp/Cargo.tomlandconfig-ui/package.json - Commits with message
chore: bump version to 0.5.0 - Pushes to remote
- Creates and pushes git tag
v0.5.0
The tag triggers GitHub Actions to build multi-platform binaries, Docker images, and packages.
IDE Setup
VS Code
Recommended extensions:
rust-analyzer: Rust language supportcrates: Dependency version hintsTOML Language Support: Cargo.toml editing
settings.json:
{
"rust-analyzer.checkOnSave.command": "clippy",
"rust-analyzer.cargo.features": "all"
}
IntelliJ / RustRover
- Install Rust plugin
- Enable clippy on save
Troubleshooting
Build errors
# Clean build artifacts
cd rust-mcp && cargo clean
# Update dependencies
cargo update
# Rebuild
cargo build
Missing static/dist
If you get “static/dist directory not found” at runtime, rebuild the React UI:
cd config-ui && npm ci && npm run build
The built assets must be at rust-mcp/static/dist/ before the Rust binary runs.
Missing system libraries (Linux)
# Debian/Ubuntu
sudo apt install build-essential libssl-dev pkg-config
# Fedora
sudo dnf install gcc openssl-devel
Slow compilation
- Use
cargo build(debug) instead ofcargo build --release - Consider sccache
Architecture Overview
This document describes the internal architecture of odoo-rust-mcp.
High-Level Architecture
+-------------------------------------------------------------------+
| MCP Clients |
| (Cursor, Claude Desktop, Claude Code, Windsurf) |
+-------------------------------+-----------------------------------+
|
v
+-------------------------------------------------------------------+
| Transport Layer |
| +----------+-----------+-----------+----------+ |
| | stdio | HTTP | SSE | WS | |
| +----+-----+-----+-----+-----+-----+----+----+ |
| +------------+-----+-----+-----------+ |
+-------------------------------+-----------------------------------+
v
+-------------------------------------------------------------------+
| MCP Protocol Handler |
| +--------------+ +--------------+ +--------------+ |
| | Tools | | Prompts | | Resources | |
| +--------------+ +--------------+ +--------------+ |
+-------------------------------+-----------------------------------+
v
+-------------------------------------------------------------------+
| Operation Dispatcher |
| +--------+ +------+ +--------+ +---------+ +---------+ |
| | search | | read | | create | | execute | | cleanup | ... |
| +--------+ +------+ +--------+ +---------+ +---------+ |
+-------------------------------+-----------------------------------+
v
+-------------------------------------------------------------------+
| Odoo Client Pool |
| +--------------------+ +--------------------+ |
| | JSON-2 (v19+) | | JSON-RPC (<19) | |
| | client.rs | | legacy_client.rs | |
| +--------------------+ +--------------------+ |
+-------------------------------+-----------------------------------+
v
+-------------------------------------------------------------------+
| Odoo Instance(s) |
| Production | Staging | Development |
+-------------------------------------------------------------------+
Directory Structure
odoo-rust-mcp/
+-- rust-mcp/ # Main Rust project
| +-- src/
| | +-- main.rs # CLI entry: transport selection, config setup
| | +-- lib.rs # Library root: pub mod cleanup, config_manager, mcp, odoo
| | +-- bin/ # Additional binaries (ws_smoke_client)
| | +-- mcp/ # MCP protocol implementation
| | | +-- mod.rs # McpOdooHandler (ServerHandler trait impl)
| | | +-- tools.rs # Tool dispatch: execute_op() router, 22 op handlers
| | | +-- registry.rs # Config loading, tool/prompt definitions, guard evaluation
| | | +-- cache.rs # MetadataCache: TTL-based in-memory caching
| | | +-- cursor_stdio.rs # stdio transport for Cursor/Claude Desktop
| | | +-- http.rs # Axum HTTP server, Streamable HTTP + SSE transport
| | | +-- prompts.rs # MCP prompt handling
| | | +-- resources.rs # odoo:// URI resource definitions
| | | +-- runtime.rs # ServerCompat wrapper for MCP SDK
| | +-- odoo/ # Odoo API clients
| | | +-- mod.rs # Module exports
| | | +-- unified_client.rs # OdooClient trait (abstraction over both clients)
| | | +-- client.rs # Odoo 19+ JSON-2 API client (API key auth)
| | | +-- legacy_client.rs # Odoo <19 JSON-RPC client (username/password)
| | | +-- config.rs # Instance config parsing, OdooProtocol enum
| | | +-- types.rs # OdooError, shared serialization types
| | +-- config_manager/ # Web UI backend (port 3008)
| | | +-- mod.rs # Module exports
| | | +-- manager.rs # Config CRUD (load/save instances, tools, prompts, server)
| | | +-- server.rs # Axum HTTP server: REST API, static file serving
| | | +-- watcher.rs # File system watcher for hot-reload
| | +-- cleanup/ # Database cleanup operations (guarded)
| | +-- mod.rs # Module exports
| | +-- database.rs # Database cleanup tool
| | +-- deep.rs # Deep record cleanup with relationships
| +-- config/ # Runtime-editable config files
| | +-- tools.json # 24 tool definitions
| | +-- prompts.json # 7 prompt definitions
| | +-- server.json # Server metadata
| +-- config-defaults/ # Seed defaults (embedded in binary via include_dir!)
| +-- tests/ # Integration tests (20+ test files)
| +-- static/dist/ # Built React UI (generated by config-ui build)
| +-- Cargo.toml # Rust dependencies (edition 2024)
| +-- Dockerfile # Multi-stage Docker build
+-- config-ui/ # React TypeScript config UI
| +-- src/
| | +-- App.tsx # Main app (5-tab layout with auth)
| | +-- components/ # React components
| | | +-- tabs/ # InstancesTab, ToolsTab, PromptsTab, ServerTab, SecurityTab
| | +-- hooks/ # useConfig, useAuth custom hooks
| | +-- __tests__/ # Vitest tests
| | +-- types.ts # TypeScript types mirroring Rust config structs
| +-- vite.config.ts # Builds to ../rust-mcp/static/dist
| +-- vitest.config.ts # Test config (Istanbul coverage)
| +-- package.json # Version must match Cargo.toml
+-- config/ # Top-level config (same as rust-mcp/config)
+-- k8s/ # Kubernetes manifests (7 files)
+-- helm/ # Helm chart
+-- scripts/ # Release, install, version bump scripts
+-- .github/workflows/ # CI/CD (ci.yml, release.yml)
Key Components
1. Transport Layer
Handles communication with MCP clients.
| Transport | Module | Use Case |
|---|---|---|
| stdio | mcp/cursor_stdio.rs | Local AI clients (Cursor, Claude Desktop, Claude Code) |
| HTTP | mcp/http.rs | Remote access, webhooks, SSE streaming |
| WebSocket | mcp/http.rs (ws mode) | Real-time bidirectional integrations |
2. MCP Protocol Handler (mcp/mod.rs)
Implements the ServerHandler trait with:
initialize: Capability negotiation (tools, prompts, resources)tools/list: Returns tools from Registry (filtered by guards)tools/call: Routes toexecute_op()inmcp/tools.rsprompts/list,prompts/get: Returns prompts from Registryresources/list,resources/read: Returns Odoo instance metadata viaodoo://URIsping: Health check
3. Registry (mcp/registry.rs)
Centralized configuration store:
- Loads
tools.json,prompts.json,server.jsonfrom config directory - Evaluates guards (
requiresEnv,requiresEnvTrue) to filter tool visibility - Provides
Arc<Registry>for thread-safe shared access - Supports hot-reload when files change
4. Operation Dispatcher (mcp/tools.rs)
Maps tool op.type to handler functions. All 22 operation types:
| Operation | Handler | Description |
|---|---|---|
search | op_search() | Search for record IDs |
search_read | op_search_read() | Search and read records |
read | op_read() | Read records by IDs |
create | op_create() | Create new record |
write | op_write() | Update records |
unlink | op_unlink() | Delete records |
search_count | op_search_count() | Count records |
execute | op_execute() | Execute model method |
workflow_action | op_workflow_action() | Call workflow action |
generate_report | op_generate_report() | Generate PDF report |
get_model_metadata | op_get_model_metadata() | Get model fields |
list_models | op_list_models() | List available models |
check_access | op_check_access() | Check permissions |
create_batch | op_create_batch() | Batch create records |
read_group | op_read_group() | Aggregate data |
name_search | op_name_search() | Autocomplete search |
name_get | op_name_get() | Get display names |
default_get | op_default_get() | Get default values |
copy | op_copy() | Duplicate record |
onchange | op_onchange() | Simulate form onchange |
database_cleanup | op_database_cleanup() | Clean database |
deep_cleanup | op_deep_cleanup() | Deep clean database |
5. Odoo Client Pool (mcp/tools.rs)
OdooClientPool is a cloneable, thread-safe wrapper around the loaded instance configuration:
#![allow(unused)]
fn main() {
pub struct OdooClientPool {
env: Arc<RwLock<OdooEnvConfig>>, // hot-reloadable config
clients: Arc<Mutex<HashMap<String, OdooClient>>>, // cached per-instance clients
pub metadata_cache: MetadataCache,
}
}
Key behaviours:
| Method | Description |
|---|---|
from_env() | Loads instances from ODOO_INSTANCES_JSON / env vars |
get(name) | Returns cached client or creates a new one; reads config under RwLock |
instance_names() | Returns available instance names (lock-free try_read) |
reload() | Hot-reload: re-reads ODOO_INSTANCES_JSON, swaps config under write lock, clears client cache |
Bidirectional instance sync
The pool and Config UI stay in sync through two paths:
-
Config UI → pool (
server.rs → pool.reload()): Afterupdate_instancessavesinstances.json, it callspool.reload().await. Thestd::sync::RwLockwrite guard is released before the asyncclients.lock().awaitto keep the futureSend-safe. -
Env vars →
instances.json(main.rs → sync_env_instances_to_file()): At startup, ifODOO_INSTANCEScontains instances not yet ininstances.json, they are merged in additively so they appear in the Config UI.
Thread-safety note
OdooClientPool uses std::sync::RwLock (not Tokio’s) for the config field because instance_names() is called from synchronous contexts. The lock is never held across an .await point.
6. Metadata Cache (mcp/cache.rs)
- TTL-based in-memory cache for
fields_getresults - Key:
(instance_name, model_name)tuple - Thread-safe via
Arc<RwLock<HashMap>> - Automatic expiration with configurable TTL
7. Config Manager
Web-based configuration UI on port 3008.
- Manager (
manager.rs): CRUD operations for JSON config files with backup/rollback - Watcher (
watcher.rs): File system monitoring; triggers Registry reload fortools.json/prompts.json/server.json; notifies the pool wheninstances.jsonchanges - Server (
server.rs): Axum HTTP server with:- REST API for all config CRUD and auth management
- Instance connection test endpoint (
POST /api/config/instances/{name}/test) - Static file serving for the React UI (
/viaServeDir) - Optional docs serving at
/docs/whendocs/book/is present
The AppState struct carries an Option<OdooClientPool> so the REST handlers can trigger pool.reload() when instances are saved:
#![allow(unused)]
fn main() {
struct AppState {
config_manager: ConfigManager,
config_watcher: Arc<ConfigWatcher>,
sessions: Arc<RwLock<HashMap<String, SessionInfo>>>,
auth_config: DynamicAuthConfig,
env_file_path: PathBuf,
http_auth_config: Option<HttpAuthConfig>,
pool: Option<OdooClientPool>, // for instance hot-reload
}
}
Data Flow
Tool Call Flow
1. Client sends tools/call request
|
2. Transport layer receives and deserializes
|
3. McpOdooHandler.handle_method() dispatches to call_tool()
|
4. Registry looks up tool definition from tools.json
|
5. execute_op() maps op.type to handler function
|
6. Handler extracts args using JSON pointer mapping
|
7. OdooClientPool.get(instance) returns appropriate client
|
8. Client makes HTTP request to Odoo (JSON-2 or JSON-RPC)
|
9. Response transformed to MCP content format
|
10. Result returned to client
Configuration Hot-Reload Flow
Tools / Prompts / Server config:
1. User saves via Config UI (or edits file directly)
|
2. ConfigWatcher detects change
|
3. Registry reloads tool/prompt/server definitions
|
4. Next MCP tools/list or tools/call uses updated config
(no restart needed)
Instances:
1. User saves instances in Config UI
|
2. ConfigManager writes instances.json
|
3. update_instances handler calls pool.reload().await
|
4. OdooClientPool re-reads ODOO_INSTANCES_JSON,
swaps OdooEnvConfig under RwLock, clears client cache
|
5. Next tool call uses updated credentials immediately
Thread Safety
| Shared State | Mechanism | Notes |
|---|---|---|
| Registry (tools/prompts/server) | Arc<RwLock<T>> (Tokio) | Read-heavy; write on reload |
| OdooClientPool.env | Arc<std::sync::RwLock<T>> | Sync lock; never held across .await |
| OdooClientPool.clients | Arc<tokio::sync::Mutex<T>> | Async lock; cleared on reload |
| MetadataCache | Arc<RwLock<HashMap>> (Tokio) | TTL-based; per (instance, model) key |
| Sessions (Config UI) | Arc<tokio::sync::RwLock<T>> | 24-hour session tokens |
Error Handling
All errors propagate as MCP error responses:
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32000,
"message": "Odoo error: Access Denied"
}
}
Error categories:
| Code | Category |
|---|---|
| -32700 | Parse error |
| -32600 | Invalid request |
| -32601 | Method not found |
| -32602 | Invalid params |
| -32603 | Internal error |
| -32000 | Odoo error |
| -32001 | Authentication error |
| -32002 | Access denied |
API Reference
Internal API documentation for odoo-rust-mcp developers.
MCP Protocol Methods
initialize
Called by clients to establish a session.
Request:
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-03-26",
"clientInfo": {
"name": "cursor",
"version": "1.0.0"
}
}
}
Response:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2025-03-26",
"serverInfo": {
"name": "odoo-rust-mcp",
"version": "0.5.0"
},
"capabilities": {
"tools": {},
"prompts": {},
"resources": {}
}
}
}
tools/list
List available tools.
Request:
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/list"
}
Response:
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"tools": [
{
"name": "odoo_search",
"description": "Search for Odoo records...",
"inputSchema": { ... }
}
]
}
}
tools/call
Execute a tool.
Request:
{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "odoo_search_read",
"arguments": {
"instance": "production",
"model": "res.partner",
"domain": [["is_company", "=", true]],
"fields": ["name", "email"],
"limit": 10
}
}
}
Response:
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"content": [
{
"type": "text",
"text": "{\"records\":[...],\"count\":10}"
}
]
}
}
prompts/list
List available prompts.
Response:
{
"result": {
"prompts": [
{
"name": "odoo_common_models",
"description": "List of commonly used Odoo models"
}
]
}
}
prompts/get
Get prompt content.
Request:
{
"method": "prompts/get",
"params": {
"name": "odoo_domain_filters"
}
}
resources/list
List Odoo resources.
Response:
{
"result": {
"resources": [
{ "uri": "odoo://instances", "name": "Odoo Instances" },
{ "uri": "odoo://production/models", "name": "Models" }
]
}
}
Operation Types
Internal operation types mapped from tools.json via op.type:
| Type | Tool Name | Description |
|---|---|---|
search | odoo_search | Search for record IDs |
search_read | odoo_search_read | Search and read records |
read | odoo_read | Read records by IDs |
create | odoo_create | Create new record |
write | odoo_update | Update records |
unlink | odoo_delete | Delete records |
search_count | odoo_count | Count records |
execute | odoo_execute | Execute model method |
workflow_action | odoo_workflow_action | Call workflow action |
generate_report | odoo_generate_report | Generate PDF report |
get_model_metadata | odoo_get_model_metadata | Get model fields |
list_models | odoo_list_models | List available models |
check_access | odoo_check_access | Check permissions |
create_batch | odoo_create_batch | Batch create records |
read_group | odoo_read_group | Aggregate data |
name_search | odoo_name_search | Autocomplete search |
name_get | odoo_name_get | Get display names |
default_get | odoo_default_get | Get default values |
copy | odoo_copy | Duplicate record |
onchange | odoo_onchange | Simulate onchange |
database_cleanup | odoo_database_cleanup | Clean database |
deep_cleanup | odoo_deep_cleanup | Deep clean database |
MCP HTTP Endpoints
When running in HTTP transport mode (--transport http):
MCP Streamable HTTP (per MCP spec)
| Endpoint | Method | Description |
|---|---|---|
/mcp | POST | Send JSON-RPC messages |
/mcp | GET | Open SSE stream for server-to-client notifications |
/mcp | DELETE | Terminate a session |
Legacy Endpoints
| Endpoint | Method | Description |
|---|---|---|
/sse | GET | Legacy SSE transport |
/messages | POST | Legacy message endpoint |
Public Endpoints (no auth)
| Endpoint | Method | Description |
|---|---|---|
/health | GET | Health check |
/openapi.json | GET | OpenAPI specification |
Health Check Response
{
"service": "odoo-rust-mcp",
"status": "ok"
}
Config UI API (Port 3008)
Authentication
The Config UI uses Bearer token authentication. Token is stored in localStorage as mcp_config_token and sent via the Authorization: Bearer {token} header.
Public Endpoints (no auth required)
| Endpoint | Method | Description |
|---|---|---|
/health | GET | Config server health check |
/api/auth/status | GET | Check authentication status |
/api/auth/login | POST | Login with username/password |
/api/auth/logout | POST | Logout and invalidate token |
Protected Endpoints (require auth)
| Endpoint | Method | Description |
|---|---|---|
/api/config/instances | GET | Get instances configuration |
/api/config/instances | POST | Save instances configuration; triggers OdooClientPool.reload() |
/api/config/instances/{name}/test | POST | Test connectivity for a specific instance |
/api/config/tools | GET | Get tools configuration |
/api/config/tools | POST | Save tools configuration |
/api/config/prompts | GET | Get prompts configuration |
/api/config/prompts | POST | Save prompts configuration |
/api/config/server | GET | Get server configuration |
/api/config/server | POST | Save server configuration |
/api/auth/change-password | POST | Change Config UI password |
/api/auth/mcp-auth-status | GET | Get MCP HTTP auth status |
/api/auth/mcp-auth-enabled | POST | Enable/disable MCP HTTP auth |
/api/auth/generate-mcp-token | POST | Generate new MCP auth token |
Instance connection test
POST /api/config/instances/{name}/test
Tests the connection to a named Odoo instance by loading its config from instances.json, creating a client, and calling health_check() (which runs a lightweight search_count on ir.model). The test runs server-side so it bypasses browser CORS restrictions.
Response (success):
{ "ok": true, "latency_ms": 142 }
Response (failure):
{ "ok": false, "error": "Connection refused (os error 111)" }
Static Files and Documentation
| Path | Source | Notes |
|---|---|---|
/ (fallback) | static/dist/ | Built React UI via ServeDir |
/docs/ | docs/book/ | Built mdBook docs; only mounted when directory exists |
The documentation route is discovered at startup by find_docs_dir(), which searches for docs/book/ relative to the working directory and executable path. If the docs have not been built (mdbook build), the /docs/ route is simply not registered — the server starts normally.
Config Server Health Check Response
{
"service": "odoo-rust-mcp-config",
"status": "ok"
}
Odoo API Mapping
JSON-2 API (v19+)
POST /json/2/{db}/{model}/{method}
Authorization: Bearer {api_key}
Content-Type: application/json
JSON-RPC API (<v19)
POST /jsonrpc
Content-Type: application/json
{
"jsonrpc": "2.0",
"method": "call",
"params": {
"service": "object",
"method": "execute_kw",
"args": [db, uid, password, model, method, args, kwargs]
}
}
Error Codes
| Code | Category | Description |
|---|---|---|
| -32700 | Parse error | Invalid JSON |
| -32600 | Invalid request | Malformed JSON-RPC |
| -32601 | Method not found | Unknown MCP method |
| -32602 | Invalid params | Missing or invalid parameters |
| -32603 | Internal error | Server-side error |
| -32000 | Odoo error | Error from Odoo API |
| -32001 | Authentication error | Invalid credentials |
| -32002 | Access denied | Insufficient permissions |
Testing Guide
How to run and write tests for odoo-rust-mcp.
Rust Tests
Unit Tests
cd rust-mcp
# Run all tests
cargo test
# Run with output
cargo test -- --nocapture
# Run specific test
cargo test test_search_operation
# Run tests with warnings as errors (same as CI)
RUSTFLAGS='-Dwarnings' cargo test
Config Manager Tests
The config manager has dedicated unit and integration tests that run with sequential threading to avoid port conflicts:
cd rust-mcp
# Unit tests (in-process)
cargo test --lib config_manager -- --nocapture --test-threads=1
# Integration tests (spawns actual HTTP server)
cargo test --test config_manager -- --nocapture
Cross-Platform Tests
Tests run on Linux, macOS, and Windows in CI:
# Run all tests with all features enabled (same as CI matrix)
cargo test --all-features
Config UI Tests
The React Config UI uses Vitest with Istanbul coverage:
cd config-ui
# Run tests
npm test
# Run tests with coverage report
npm run test:coverage
# Type checking (not tests, but catches errors)
npm run typecheck
# Linting
npm run lint
Coverage output is written to config-ui/coverage/ in Cobertura XML format.
Smoke Testing
WebSocket Smoke Client
End-to-end validation of MCP operations against a running server:
cd rust-mcp
cargo run --release --bin ws_smoke_client -- \
--url ws://127.0.0.1:8787 \
--instance default \
--model res.partner
Expected output:
tools/list: 24 tools
- odoo_search
- odoo_search_read
- odoo_read
- ...
odoo_count result: {"count":18}
odoo_search_read count: 2
prompts/list: odoo_common_models, odoo_domain_filters, odoo_field_types, odoo_workflow_states, odoo_read_group, odoo_context, odoo_api_tips, odoo_owl_components, odoo_assets_and_bundles, odoo_frontend_contexts, odoo_qweb_and_templates
HTTP Health Check
curl http://127.0.0.1:8787/health
Expected:
{
"service": "odoo-rust-mcp",
"status": "ok"
}
Config UI Health Check
curl http://127.0.0.1:3008/health
Expected:
{
"service": "odoo-rust-mcp-config",
"status": "ok"
}
Manual Testing Checklist
Transport Modes
- stdio: Test with Cursor or Claude Desktop
- HTTP: Test with curl or Postman
- WebSocket: Test with ws_smoke_client
- SSE: Test streaming responses
Authentication
- Odoo 19+: Test API key authentication (JSON-2 client)
- Odoo <19: Test username/password authentication (JSON-RPC client)
- Multi-instance: Test switching between instances
- MCP HTTP auth: Test Bearer token authentication
Tools
- Read tools: search, search_read, read, count, name_search, name_get
- Write tools: create, create_batch, update, delete, copy
- Workflow tools: execute, workflow_action
- Metadata tools: list_models, get_model_metadata, default_get, check_access
- Advanced tools: read_group, onchange, generate_report
- Cleanup tools: database_cleanup, deep_cleanup (requires
ODOO_ENABLE_CLEANUP_TOOLS=true)
Config UI
- Login with default credentials
- Change password
- Edit instances (add, modify, remove)
- Edit tools (enable/disable)
- Edit prompts
- Edit server metadata
- Enable/disable MCP HTTP auth
- Generate MCP auth token
Writing Tests
Unit Test Example
#![allow(unused)]
fn main() {
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_domain() {
let domain = r#"[["name", "=", "Test"]]"#;
let result = parse_domain(domain);
assert!(result.is_ok());
}
#[test]
fn test_invalid_domain() {
let domain = "invalid";
let result = parse_domain(domain);
assert!(result.is_err());
}
}
}
Async Test Example
#![allow(unused)]
fn main() {
#[tokio::test]
async fn test_odoo_client() {
let client = OdooClient::new(config).await.unwrap();
let result = client.search("res.partner", &[]).await;
assert!(result.is_ok());
}
}
Test Coverage
Rust Coverage (cargo-tarpaulin)
cd rust-mcp
# Install tarpaulin
cargo install cargo-tarpaulin
# Generate HTML report
cargo tarpaulin --all-targets --all-features --out Html
# Generate Cobertura XML (for CI upload)
cargo tarpaulin --all-targets --all-features --out xml --output-dir coverage
TypeScript Coverage (Istanbul via Vitest)
cd config-ui
npm run test:coverage
Coverage reports are uploaded to Codecov in CI.
CI/CD Pipeline
GitHub Actions runs on every push to main and on pull requests. The pipeline has 4 stages:
Stage 1: Build UI
Builds the React Config UI first (required dependency for all other jobs):
build-ui
-> npm ci
-> npm run build
-> Upload artifact: config-ui-dist
Stage 2: Parallel Quality Checks
All run in parallel after build-ui completes:
| Job | Description |
|---|---|
| check | cargo check --all-features |
| fmt | cargo fmt --all --check |
| clippy | cargo clippy -- -D warnings |
| test | cargo test on Linux, macOS, Windows |
| ui-tests | npm test (Vitest) |
| coverage | Rust (tarpaulin) + TypeScript (Istanbul), uploaded to Codecov |
| security | cargo audit |
| config-tests | Config manager unit + integration tests |
| config-integration | Builds release binary, starts HTTP server, tests endpoints |
| helm-validation | helm lint + helm template validation |
| docker-test | Docker image build test |
Stage 3: Build Release Binary
Runs after quality checks pass:
build-release (needs: build-ui, check, fmt, clippy, ui-tests)
-> Download config-ui-dist artifact
-> cargo build --release
Stage 4: Service Integration Tests
Tests real deployment scenarios:
| Job | Description |
|---|---|
| test-systemd-service | Installs binary + systemd unit, tests lifecycle (start/restart/stop), tests HTTP + MCP endpoints |
| test-macos-service | Builds and runs binary on macOS, tests HTTP + MCP endpoints |
Pipeline Diagram
build-ui
|
+---> check --------+
+---> fmt ----------+
+---> clippy -------+---> build-release ---> test-systemd-service
+---> ui-tests -----+
+---> test (matrix)
+---> coverage
+---> security
+---> config-tests
+---> config-integration
+---> helm-validation
+---> docker-test
+---> test-macos-service
Test Configuration
For tests requiring an Odoo connection, set environment variables:
export TEST_ODOO_URL=http://localhost:8069
export TEST_ODOO_DB=test_db
export TEST_ODOO_API_KEY=test_key
Or use .env.test:
TEST_ODOO_URL=http://localhost:8069
TEST_ODOO_DB=test_db
TEST_ODOO_VERSION=18
TEST_ODOO_USERNAME=admin
TEST_ODOO_PASSWORD=admin
Debugging Tests
# Run with debug output
RUST_LOG=debug cargo test -- --nocapture
# Run single test with backtrace
RUST_BACKTRACE=1 cargo test test_name -- --nocapture
Contributing Guide
Thank you for your interest in contributing to odoo-rust-mcp!
Quick Start
- Fork the repository on GitHub
- Clone your fork locally
- Create a feature branch
- Make your changes
- Test your changes
- Submit a pull request
Development Workflow
1. Fork and Clone
git clone https://github.com/YOUR-USERNAME/odoo-rust-mcp.git
cd odoo-rust-mcp
2. Create a Branch
git checkout -b feature/your-feature-name
# or
git checkout -b fix/your-bug-fix
3. Build the Project
The React UI must be built before the Rust binary. See Building from Source for details.
# Build config UI first
cd config-ui && npm ci && npm run build && cd ..
# Build Rust server
cd rust-mcp && cargo build && cd ..
4. Make Changes
Follow the coding standards below.
5. Test Your Changes
# Rust tests and linting
cd rust-mcp
cargo test
cargo clippy -- -D warnings
cargo fmt --check
# Config UI tests and linting (if you changed config-ui/)
cd ../config-ui
npm test
npm run typecheck
npm run lint
6. Commit
git add .
git commit -m "Add feature: description of what you did"
7. Push and Create PR
git push origin feature/your-feature-name
Then create a Pull Request on GitHub.
Agentic Kanban Workflow
This repository also carries a repo-local Agentic Kanban workspace in .agentkanban/.
- The current board profile is
lite, so the working lane flow isbacklog -> in-progress -> done. - For non-trivial changes, prefer spec-driven work with:
.agentkanban/specs/<capability>/spec.md.agentkanban/changes/<task-slug>/proposal.md.agentkanban/changes/<task-slug>/design.md.agentkanban/changes/<task-slug>/tasks.md
changes/<task-slug>/tasks.mdis the authoritative checklist for spec-driven work.
See Agentic Kanban Workflow for the full repo-local conventions.
Coding Standards
Rust Style
- Follow Rust API Guidelines
- Use
cargo fmtfor formatting - Fix all
cargo clippywarnings (CI runs with-D warnings) - Use
Result<T, E>for error handling (avoid panics) - Document public APIs with doc comments
- Edition: Rust 2024 (requires rustc 1.85+)
Example documentation:
#![allow(unused)]
fn main() {
/// Brief description.
///
/// Detailed description if needed.
///
/// # Examples
///
/// ```
/// let result = my_function();
/// ```
pub fn my_function() -> Result<()> {
// ...
}
}
TypeScript Style (Config UI)
- React 18 with functional components and hooks
- TypeScript strict mode
- Tailwind CSS for styling
- Vitest for tests
- Follow existing patterns in
config-ui/src/
Commit Messages
- Start with a verb: “Add”, “Fix”, “Update”, “Remove”
- Keep first line under 72 characters
- Reference issues: “Fixes #123”
Examples:
Add support for Odoo 19 JSON-2 API
Implements authentication via API keys and uses the new /json/2/ endpoint.
Fixes #42
Adding New Tools
Tools are defined declaratively in tools.json. No Rust code changes are needed for simple tools.
1. Add Tool Definition
Edit rust-mcp/config/tools.json:
{
"name": "odoo_my_new_tool",
"description": "Description of what the tool does",
"inputSchema": {
"type": "object",
"properties": {
"instance": { "type": "string" },
"model": { "type": "string" }
},
"required": ["instance", "model"]
},
"op": {
"type": "my_operation_type",
"map": {
"instance": "/instance",
"model": "/model"
}
}
}
2. Implement Operation Handler (if new op type)
If your tool uses an existing op.type (e.g., search_read, execute), no Rust changes are needed.
For a new operation type, add a handler in rust-mcp/src/mcp/tools.rs:
- Add a new
op_my_operation()async function - Add the type to the
execute_op()match statement - Write tests
3. Update Seed Defaults
Copy changes to rust-mcp/config-defaults/tools.json so new installations get the tool.
4. Add Tests
Write tests for the new tool.
5. Update Documentation
Update docs/src/functional/tools-reference.md.
Note: Avoid
anyOf,oneOf,allOf,$refin JSON Schema – Cursor rejects these.
Adding New Prompts
Edit rust-mcp/config/prompts.json:
{
"name": "my_new_prompt",
"description": "What this prompt provides",
"content": "The actual prompt content..."
}
Update config-defaults/prompts.json and documentation.
Contributing to Config UI
The Config UI is in config-ui/ and uses React 18 + TypeScript + Vite + Tailwind CSS.
Development Workflow
cd config-ui
# Install dependencies
npm ci
# Start development server with HMR
npm run dev
# Access at http://localhost:5173
# In another terminal, start the Rust server
cd rust-mcp && cargo run -- --transport http --listen 127.0.0.1:8787
Project Structure
config-ui/src/
+-- App.tsx # Main app (5-tab layout with auth)
+-- components/tabs/ # InstancesTab, ToolsTab, PromptsTab, ServerTab, SecurityTab
+-- hooks/ # useConfig, useAuth custom hooks
+-- __tests__/ # Vitest tests
+-- types.ts # TypeScript types mirroring Rust config structs
Adding a New Tab
- Create
config-ui/src/components/tabs/MyNewTab.tsx - Add the tab to
App.tsx - Create types in
types.tsif needed - Add tests in
__tests__/
Pull Request Checklist
Rust Changes
- Tests pass:
cargo test - Linting passes:
cargo clippy -- -D warnings - Formatting correct:
cargo fmt --check
Config UI Changes
- Tests pass:
npm test - Type checking passes:
npm run typecheck - Linting passes:
npm run lint - Production build succeeds:
npm run build
General
- Documentation updated (if applicable)
- Commit messages are clear and descriptive
- PR description explains changes
- Both
config-defaults/andconfig/updated (if adding tools/prompts)
Review Process
- Submit PR with clear description
- CI must pass (build-ui, tests, clippy, fmt, coverage)
- Maintainers review code quality and tests
- Address feedback
- Merge!
Getting Help
- Questions: GitHub Discussions
- Bugs: Issue Tracker
- Security: See SECURITY.md
License
By contributing, you agree that your contributions will be licensed under AGPL-3.0.
Agentic Kanban Workflow
This repository includes a repo-local Agentic Kanban workspace in .agentkanban/. The goal is to
keep task state, specs, and implementation notes inside the repository instead of scattering them
across transient chat history.
Active profile
The current board lives in .agentkanban/board.yaml and uses the lite profile:
backlog -> in-progress -> done
That matters because the bundled prompts and task guidance should match the board. In this repo, do
not assume planning or review lanes unless the board profile changes first.
Artifact layout
.agentkanban/
board.yaml
INSTRUCTION.md
memory.md
prompts/
specs/
changes/
tasks/
Use the directories like this:
tasks/: conversational task files and lane statespecs/: durable capability contractschanges/<task-slug>/proposal.md: why the task existschanges/<task-slug>/design.md: implementation decisions and riskschanges/<task-slug>/tasks.md: authoritative checklist for spec-driven workmemory.md: stable repo conventions worth carrying between tasks
Spec-driven development on Lite
Lite keeps the lane model small, but it still works well with SDD for larger or cross-cutting changes.
Use a spec-driven task when the work:
- spans Rust, UI, config, and docs
- has meaningful acceptance criteria
- is risky enough to need a written verify path
- is likely to be resumed or reviewed later
Recommended flow:
- Capture the task in
backlog. - Add
spec:andchange:frontmatter when the work needs durable planning. - Move the task to
in-progressonce the implementation path is clear. - Implement against
changes/<task-slug>/tasks.md. - Run the repo validation gate and update docs.
- Move the task to
done.
Prompt pack
The repo-local prompts under .agentkanban/prompts/ are tailored to this project and should remain
in sync with the board profile. The core files are:
new-task-intake.mdstage-backlog-to-in-progress.mdstage-in-progress-to-done.mdstage-blocked-and-resume.mdproduction-readiness-audit.md
They use the real build and validation commands from this repository and intentionally avoid policy
text copied from unrelated projects. Keep the pack lean: if a prompt assumes planning or review
lanes, or describes sweep-style multi-task processing that this repo does not use, remove it rather
than letting it rot.
Validation gate
For workflow and documentation changes, verify consistency at minimum. For code changes, use the real
repo gate from AGENTS.md:
cargo fmt --all --check --manifest-path rust-mcp/Cargo.toml
cargo clippy --all-features --manifest-path rust-mcp/Cargo.toml -- -D warnings
cargo test --all-features --manifest-path rust-mcp/Cargo.toml
cd config-ui && npm run lint && npm run typecheck && npm test && npm run build
If auth, transport, or config-manager behavior changes, also run a local smoke test against
/health, /mcp, and the Config UI.
Enterprise Pack Metadata and Capability Snapshots
The v0.6 scope deliberately adds only the part of enterprise packs that the current server needs: tools may declare an optional pack and requiredModules, and the server maintains a per-instance snapshot of installed Odoo modules. Instance-scoped tools/list responses hide tools whose pack is disabled or whose required modules are absent. Standard unscoped tools/list responses advertise a gated tool only when every configured instance can use it. The call path repeats the per-instance check so a stale client catalog cannot bypass it.
Snapshots are stored as module-snapshots.json beside the active tools.json. ODOO_MODULE_SNAPSHOT_TTL_SECS controls the refresh interval and defaults to 300 seconds. odoo_refresh_capabilities forces a refresh. A failed refresh marks the snapshot stale and records the error while preserving the last successful module list.
The Config UI reads the same snapshot through GET /api/config/instances/{name}/capabilities (add ?refresh=true to force a live re-scan). Each instance card shows module count, edition, staleness, and last refresh, and disabledPacks is editable per instance from the instance form. Tool cards surface each tool’s pack and requiredModules.
Per-instance pack suppression reuses toolConfig:
{
"toolConfig": {
"disabledPacks": ["inventory"]
}
}
Policy denials emit a metadata-only tool_policy_decision event with the instance, tool, pack, and reason code. Arguments and results are not logged in that event.
Behavior notes
The unscoped rule is intentionally conservative. An unscoped tools/call (no instance argument) runs against the default instance and does not re-run the per-instance capability check, so advertising a gated tool only when every instance supports it keeps that default-instance call safe. Clients that want a tool available on just one instance should pass that instance to both tools/list and tools/call.
A failed refresh advances the snapshot’s checkedAt, so an unreachable instance is treated as fresh for one TTL window rather than re-scanned on every tools/list. The last successful module list is retained, so gating stays stable while an instance is briefly down.
Deliberate deferrals
This is metadata and filtering, not a pack framework. There are no YAML manifests, resolver traits, exposure profiles, workflow runtime, compatibility adapter matrix, new confirmation-token service, or generated domain tool suites. Existing declarative tools, environment guards, read-only instances, execute allowlists, and the controlled mutation capability remain the enforcement primitives.
The larger .plan/odoo-rust-mcp-enterprise-packs-dev-docs tree is a non-canonical historical brainstorm. Add any deferred subsystem only after a concrete use case demonstrates that these small primitives are insufficient.
Live smoke check
The regular suite tests filtering and stale preservation without Odoo. A live cell can be checked explicitly:
ODOO_INSTANCES_JSON=/absolute/path/instances.json \
ODOO_E2E_INSTANCE=odoo18ce \
cargo test --manifest-path rust-mcp/Cargo.toml \
--test module_capabilities_live -- --ignored --nocapture
The smoke test refreshes the selected instance and proves that the stock-gated tool is visible exactly when stock is installed.
For an isolated inline definition, pass the same JSON through ODOO_E2E_INSTANCES; the test writes it only to its temporary directory and gives it precedence over the operator’s default runtime config.