Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Odoo Rust MCP Rust Hexagon app icon

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?

AudienceUse Case
Odoo users and IT adminsQuery data, generate reports, and automate workflows through AI assistants
DevelopersBuild AI-powered Odoo integrations and extend the server
DevOps engineersDeploy and operate the MCP server in production

Technical Details


Config UI at a Glance

The built-in web UI runs at http://localhost:3008.

AreaPurpose
OverviewRuntime summary, auth posture, and config-source checks
InstancesAdd, edit, test, import, and export Odoo connections
ToolsEnable or disable tool groups and individual tools
PromptsManage built-in and custom prompts
ServerEdit server name, instructions, and protocol version
SecurityChange Config UI password and manage MCP HTTP auth
DocumentationOpen 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

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

Download the latest release for your platform:

PlatformDownload
Windows x64odoo-rust-mcp-x86_64-pc-windows-msvc.zip
macOS Intelodoo-rust-mcp-x86_64-apple-darwin.tar.gz
macOS Apple Siliconodoo-rust-mcp-aarch64-apple-darwin.tar.gz
Linux x64odoo-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 -Shortcut

The 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.

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

This guide covers the runtime configuration surface for odoo-rust-mcp.

Instance Configuration

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

FieldRequiredDefaultDescription
urlYes-Odoo server URL
dbOdoo 18 and earlier-Database name
apiKeyOdoo 19+-API key for JSON-2 authentication
versionNo-Odoo version
usernameOdoo 18 and earlier-Username for JSON-RPC auth
passwordOdoo 18 and earlier-Password for JSON-RPC auth
protocolNoautoauto, jsonrpc, or json2
tagsNo[]Manual labels used by the Config UI
readOnlyNofalseWhen 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.disabledToolsNo[]Per-instance tool denylist
toolConfig.executeAllowlistNo[]Required for odoo_execute: [{ "model": "...", "methods": ["..."] }]. Empty denies all execute calls.
timeout_msNo30000Request timeout in milliseconds
max_retriesNo2Maximum retry attempts

Protocol Selection

By default, the server auto-detects the protocol based on available credentials:

ConditionProtocol Used
apiKey presentJSON-2 API (Odoo 19+)
username + password + version presentJSON-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

VariableDefaultDescription
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

VariableDefaultDescription
ODOO_ENABLE_WRITE_TOOLSfalseEnable create, update, delete, workflow, copy tools
ODOO_ENABLE_EXECUTE_TOOLfalseEnable odoo_execute (still requires a non-empty per-instance executeAllowlist)
ODOO_ENABLE_CLEANUP_TOOLSfalseEnable cleanup tools only when ODOO_ENABLE_WRITE_TOOLS is also true; cleanup defaults to dry-run
ODOO_CAPABILITY_CONTROLLED_MODEfalseHide/reject generic mutations and expose only odoo_execute_capability
ODOO_CAPABILITY_REGISTRYRequired in controlled mode: normalized odoo-agent registry JSON
ODOO_CAPABILITY_APPROVAL_HMAC_KEYRequired in controlled mode: approval-envelope HMAC key of at least 32 bytes
ODOO_CAPABILITY_STATE_DIRRequired in controlled mode: persistent 0700 idempotency-state directory
ODOO_TIMEOUT_MS30000Request timeout in milliseconds
ODOO_MAX_RETRIES2Retry attempts
ODOO_MODULE_SNAPSHOT_TTL_SECS300Installed-module snapshot TTL; 0 refreshes every instance-scoped list

MCP Configuration

VariableDefaultDescription
MCP_TOOLS_JSONAutoPath to tools.json
MCP_PROMPTS_JSONAutoPath to prompts.json
MCP_SERVER_JSONAutoPath to server.json

Authentication (HTTP Transport)

VariableDefaultDescription
MCP_AUTH_ENABLEDfalseEnable bearer-token auth for MCP HTTP
MCP_AUTH_TOKEN-Auth token
MCP_ALLOWED_ORIGINS-Allowed CORS origins

Config UI

VariableDefaultDescription
ODOO_CONFIG_SERVER_PORT3008Config UI port
ODOO_CONFIG_DIR~/.config/odoo-rust-mcpConfig directory path
CONFIG_UI_USERNAMEadminLogin username
CONFIG_UI_PASSWORDchangemeLogin password

Logging

VariableDefaultDescription
RUST_LOGinfoLog 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

AreaPurpose
OverviewRuntime summary and posture checks
InstancesAdd, edit, test, import, and export Odoo connections
ToolsEnable or disable tool groups and individual tools
PromptsEdit prompt content and descriptions
ServerEdit server name, instructions, protocol version
SecurityChange Config UI password and manage MCP HTTP auth
DocumentationOpen the built-in docs in a separate tab

First-time Setup

  1. Open http://localhost:3008
  2. Sign in with admin / changeme
  3. Go to Security and change the default password
  4. Configure instances in Instances
  5. Optionally enable MCP HTTP auth in Security
  6. 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)

PlatformDirectory
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)

PlatformDirectory
Linux (systemd)/etc/odoo-rust-mcp/
Linux (deb)/usr/share/odoo-rust-mcp/
Windows%ProgramData%\\odoo-rust-mcp\\

Config Resolution Order

  1. Explicit environment variable path
  2. User config directory
  3. Embedded defaults

Deployment Notes

MethodBest For
Binary + stdioLocal development and single AI client use
Binary + HTTPRemote access and multiple users
DockerQuick isolated deployment
Docker ComposeMulti-service setups
Kubernetes / HelmProduction deployments
systemd / Windows ServiceBackground 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.


The left sidebar is the main workspace navigator. It is collapsible to save space.

StateBehavior
ExpandedFull width (about 244 px) and shows icon + label for each entry
CollapsedNarrow 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 localStorage and restored on the next visit.

Current sidebar entries:

SectionEntryPurpose
WorkspaceOverviewRuntime summary and quick posture checks
WorkspaceInstancesOdoo connection records
WorkspaceToolsMCP catalog toggles
WorkspacePromptsShared prompt definitions
WorkspaceDocumentationOpens /docs/ in a new tab
OperationsServerServer metadata and runtime source signals
OperationsSecurityUI password and MCP HTTP auth

The header stays intentionally quiet and focuses on:

ItemDescription
Route titleThe current workspace section
Sidebar toggleCollapse or expand the desktop sidebar
Keyboard helpOpens the shortcut reference
Theme modeChooses Light, Dark, or Auto (follow system theme)

The footer shows:

ItemDescription
Hot ReloadConfirms configuration changes apply instantly
Unsaved stateWarns 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.

FieldDescription
NameUnique identifier used in tool calls (instance)
URLOdoo server URL
DatabaseDatabase name
AuthenticationAPI Key (Odoo 19+) or Username/Password (Odoo 18 and earlier)
VersionOdoo version badge when specified
TagsOptional manual labels such as prod, staging, or finance
StatusConnection test result
ActionsTest, 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.

FieldRequiredNotes
urlYesExample: https://myodoo.com
dbOdoo 18 and earlierRequired for JSON-RPC auth
apiKeyOdoo 19+API key from Odoo settings
versionOptionalExample: 16, 17, 18, 19
usernameOdoo 18 and earlierOdoo login username
passwordOdoo 18 and earlierOdoo login password
protocolNoauto, jsonrpc, or json2
tagsNoManual labels for filtering
timeout_msNoRequest timeout, default 30000
max_retriesNoRetry 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:

GroupGatePurpose
Read OperationsNoneAlways-available read and discovery tools
Write OperationsODOO_ENABLE_WRITE_TOOLS=trueCreate, update, delete, workflow, copy
ExecuteODOO_ENABLE_EXECUTE_TOOL=true plus non-empty toolConfig.executeAllowlistodoo_execute only
Cleanup OperationsODOO_ENABLE_CLEANUP_TOOLS=trueCleanup 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.json files 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:

  1. Enable MCP auth.
  2. Generate a token.
  3. 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:

ChangeEffect
Save instancesOdooClientPool reloads and cached clients clear
Save tools or promptsRegistry reloads
Save server configServer name and instructions update
Change passwordUI auth reloads in memory
Toggle MCP authHTTP transport auth reloads

Keyboard Shortcuts

ActionShortcut
Toggle sidebarCtrl/Cmd + B
Open create flowCtrl/Cmd + N
Focus primary search/
Jump to OverviewCtrl/Cmd + 1
Jump to InstancesCtrl/Cmd + 2
Jump to ToolsCtrl/Cmd + 3
Jump to PromptsCtrl/Cmd + 4
Jump to ServerCtrl/Cmd + 5
Jump to SecurityCtrl/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)

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"]
}

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

ModelDescription
res.partnerContacts/Customers
sale.orderSales Orders
purchase.orderPurchase Orders
account.moveInvoices/Bills
stock.pickingTransfers
product.productProducts
hr.employeeEmployees
project.taskTasks

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:

DocumentStates
Sale Orderdraft → sent → sale → done / cancel
Purchase Orderdraft → sent → to approve → purchase → done
Invoicedraft → posted → cancel
Stock Pickingdraft → waiting → confirmed → assigned → done
CRM Leadlead / opportunity (type)
POS Orderdraft → 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_group for aggregation
    • Avoid search in loops
  • Field selection tips
  • Common patterns
  • Error handling

odoo_owl_components

Description: Owl component structure and debugging patterns for Odoo addons.

Covers:

  • Standard static/src JS/XML/SCSS component layout
  • /** @odoo-module **/ usage
  • setup() 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_backend
  • web.assets_frontend
  • web.assets_unit_tests
  • Manifest assets wiring 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 alignment
  • t-if, t-elif, t-foreach, t-key, and event bindings
  • Dynamic attributes with t-att-* and t-attf-*
  • Odoo 18 t-esc vs Odoo 19 t-out guidance
  • 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:

  1. List models to find relevant ones:

    List models matching "stock" in my production instance.
    
  2. Get metadata for the model of interest:

    Show me the fields for stock.picking.
    
  3. Search records to see real data:

    Show me 5 recent stock pickings with their state, partner, and scheduled date.
    
  4. 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

MethodBest For
Direct binarySimple setups, development
Install scriptLinux/macOS with systemd/launchd
DockerSingle-server production
Docker ComposeMulti-service stacks (n8n, Dify)
KubernetesCluster deployments
HelmTemplated 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

  1. Download the Windows binary from GitHub Releases
  2. Place odoo-rust-mcp.exe in a permanent location (e.g., %LOCALAPPDATA%\odoo-rust-mcp\)
  3. Copy static/dist/ alongside the binary for Config UI
  4. Add the directory to your PATH
  5. Create ~/.config/odoo-rust-mcp/instances.json with 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

FeatureValue
Base imagedebian:bookworm-slim
Usermcp (non-root)
MCP port8787
Config UI port3008
Config path/config/
Health checkPOST /mcp (ping)
Default transportHTTP 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:

ResourceLimitReservation
CPU1 core0.25 cores
Memory256 MB64 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

ValueDefaultDescription
replicaCount2Number of replicas
image.repositoryghcr.io/milzamsz/odoo-rust-mcpContainer image
odooInstances.json(example)Multi-instance JSON config
mcp.auth.enabledfalseEnable HTTP auth
mcp.auth.token“”Bearer token
configServer.enabledtrueEnable Config UI
configServer.port3008Config UI port
autoscaling.enabledfalseEnable HPA
autoscaling.maxReplicas10Max replicas
ingress.enabledfalseEnable 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.1 if 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_RETRIES for unreliable networks
  • Configure resource limits (CPU/memory)
  • Use RUST_LOG=info in production (not debug)

Monitoring

  • Configure health check probes
  • Monitor GET /health (MCP server) and GET /health (Config UI)
  • Set up log aggregation (RUST_LOG=info outputs to stdout/stderr)

Configuration

  • Use instances.json file 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:

  1. Builder stage: Installs Node.js 20, builds React UI, then builds Rust binary
  2. 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:

  1. Bumps version in rust-mcp/Cargo.toml and config-ui/package.json
  2. Commits with message chore: bump version to 0.5.0
  3. Pushes to remote
  4. 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 support
  • crates: Dependency version hints
  • TOML 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 of cargo 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.

TransportModuleUse Case
stdiomcp/cursor_stdio.rsLocal AI clients (Cursor, Claude Desktop, Claude Code)
HTTPmcp/http.rsRemote access, webhooks, SSE streaming
WebSocketmcp/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 to execute_op() in mcp/tools.rs
  • prompts/list, prompts/get: Returns prompts from Registry
  • resources/list, resources/read: Returns Odoo instance metadata via odoo:// URIs
  • ping: Health check

3. Registry (mcp/registry.rs)

Centralized configuration store:

  • Loads tools.json, prompts.json, server.json from 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:

OperationHandlerDescription
searchop_search()Search for record IDs
search_readop_search_read()Search and read records
readop_read()Read records by IDs
createop_create()Create new record
writeop_write()Update records
unlinkop_unlink()Delete records
search_countop_search_count()Count records
executeop_execute()Execute model method
workflow_actionop_workflow_action()Call workflow action
generate_reportop_generate_report()Generate PDF report
get_model_metadataop_get_model_metadata()Get model fields
list_modelsop_list_models()List available models
check_accessop_check_access()Check permissions
create_batchop_create_batch()Batch create records
read_groupop_read_group()Aggregate data
name_searchop_name_search()Autocomplete search
name_getop_name_get()Get display names
default_getop_default_get()Get default values
copyop_copy()Duplicate record
onchangeop_onchange()Simulate form onchange
database_cleanupop_database_cleanup()Clean database
deep_cleanupop_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:

MethodDescription
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:

  1. Config UI → pool (server.rs → pool.reload()): After update_instances saves instances.json, it calls pool.reload().await. The std::sync::RwLock write guard is released before the async clients.lock().await to keep the future Send-safe.

  2. Env vars → instances.json (main.rs → sync_env_instances_to_file()): At startup, if ODOO_INSTANCES contains instances not yet in instances.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_get results
  • 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 for tools.json / prompts.json / server.json; notifies the pool when instances.json changes
  • 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 (/ via ServeDir)
    • Optional docs serving at /docs/ when docs/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 StateMechanismNotes
Registry (tools/prompts/server)Arc<RwLock<T>> (Tokio)Read-heavy; write on reload
OdooClientPool.envArc<std::sync::RwLock<T>>Sync lock; never held across .await
OdooClientPool.clientsArc<tokio::sync::Mutex<T>>Async lock; cleared on reload
MetadataCacheArc<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:

CodeCategory
-32700Parse error
-32600Invalid request
-32601Method not found
-32602Invalid params
-32603Internal error
-32000Odoo error
-32001Authentication error
-32002Access 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:

TypeTool NameDescription
searchodoo_searchSearch for record IDs
search_readodoo_search_readSearch and read records
readodoo_readRead records by IDs
createodoo_createCreate new record
writeodoo_updateUpdate records
unlinkodoo_deleteDelete records
search_countodoo_countCount records
executeodoo_executeExecute model method
workflow_actionodoo_workflow_actionCall workflow action
generate_reportodoo_generate_reportGenerate PDF report
get_model_metadataodoo_get_model_metadataGet model fields
list_modelsodoo_list_modelsList available models
check_accessodoo_check_accessCheck permissions
create_batchodoo_create_batchBatch create records
read_groupodoo_read_groupAggregate data
name_searchodoo_name_searchAutocomplete search
name_getodoo_name_getGet display names
default_getodoo_default_getGet default values
copyodoo_copyDuplicate record
onchangeodoo_onchangeSimulate onchange
database_cleanupodoo_database_cleanupClean database
deep_cleanupodoo_deep_cleanupDeep clean database

MCP HTTP Endpoints

When running in HTTP transport mode (--transport http):

MCP Streamable HTTP (per MCP spec)

EndpointMethodDescription
/mcpPOSTSend JSON-RPC messages
/mcpGETOpen SSE stream for server-to-client notifications
/mcpDELETETerminate a session

Legacy Endpoints

EndpointMethodDescription
/sseGETLegacy SSE transport
/messagesPOSTLegacy message endpoint

Public Endpoints (no auth)

EndpointMethodDescription
/healthGETHealth check
/openapi.jsonGETOpenAPI 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)

EndpointMethodDescription
/healthGETConfig server health check
/api/auth/statusGETCheck authentication status
/api/auth/loginPOSTLogin with username/password
/api/auth/logoutPOSTLogout and invalidate token

Protected Endpoints (require auth)

EndpointMethodDescription
/api/config/instancesGETGet instances configuration
/api/config/instancesPOSTSave instances configuration; triggers OdooClientPool.reload()
/api/config/instances/{name}/testPOSTTest connectivity for a specific instance
/api/config/toolsGETGet tools configuration
/api/config/toolsPOSTSave tools configuration
/api/config/promptsGETGet prompts configuration
/api/config/promptsPOSTSave prompts configuration
/api/config/serverGETGet server configuration
/api/config/serverPOSTSave server configuration
/api/auth/change-passwordPOSTChange Config UI password
/api/auth/mcp-auth-statusGETGet MCP HTTP auth status
/api/auth/mcp-auth-enabledPOSTEnable/disable MCP HTTP auth
/api/auth/generate-mcp-tokenPOSTGenerate 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

PathSourceNotes
/ (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

CodeCategoryDescription
-32700Parse errorInvalid JSON
-32600Invalid requestMalformed JSON-RPC
-32601Method not foundUnknown MCP method
-32602Invalid paramsMissing or invalid parameters
-32603Internal errorServer-side error
-32000Odoo errorError from Odoo API
-32001Authentication errorInvalid credentials
-32002Access deniedInsufficient 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:

JobDescription
checkcargo check --all-features
fmtcargo fmt --all --check
clippycargo clippy -- -D warnings
testcargo test on Linux, macOS, Windows
ui-testsnpm test (Vitest)
coverageRust (tarpaulin) + TypeScript (Istanbul), uploaded to Codecov
securitycargo audit
config-testsConfig manager unit + integration tests
config-integrationBuilds release binary, starts HTTP server, tests endpoints
helm-validationhelm lint + helm template validation
docker-testDocker 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:

JobDescription
test-systemd-serviceInstalls binary + systemd unit, tests lifecycle (start/restart/stop), tests HTTP + MCP endpoints
test-macos-serviceBuilds 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

  1. Fork the repository on GitHub
  2. Clone your fork locally
  3. Create a feature branch
  4. Make your changes
  5. Test your changes
  6. 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 is backlog -> 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.md is 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 fmt for formatting
  • Fix all cargo clippy warnings (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:

  1. Add a new op_my_operation() async function
  2. Add the type to the execute_op() match statement
  3. 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, $ref in 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

  1. Create config-ui/src/components/tabs/MyNewTab.tsx
  2. Add the tab to App.tsx
  3. Create types in types.ts if needed
  4. 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/ and config/ updated (if adding tools/prompts)

Review Process

  1. Submit PR with clear description
  2. CI must pass (build-ui, tests, clippy, fmt, coverage)
  3. Maintainers review code quality and tests
  4. Address feedback
  5. Merge!

Getting Help


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 state
  • specs/: durable capability contracts
  • changes/<task-slug>/proposal.md: why the task exists
  • changes/<task-slug>/design.md: implementation decisions and risks
  • changes/<task-slug>/tasks.md: authoritative checklist for spec-driven work
  • memory.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:

  1. Capture the task in backlog.
  2. Add spec: and change: frontmatter when the work needs durable planning.
  3. Move the task to in-progress once the implementation path is clear.
  4. Implement against changes/<task-slug>/tasks.md.
  5. Run the repo validation gate and update docs.
  6. 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.md
  • stage-backlog-to-in-progress.md
  • stage-in-progress-to-done.md
  • stage-blocked-and-resume.md
  • production-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.