## odoo-rust-mcp 🚗 [![CI](https://github.com/rachmataditiya/odoo-rust-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/rachmataditiya/odoo-rust-mcp/actions/workflows/ci.yml) [![Release](https://github.com/rachmataditiya/odoo-rust-mcp/actions/workflows/release.yml/badge.svg)](https://github.com/rachmataditiya/odoo-rust-mcp/actions/workflows/release.yml) [![codecov](https://codecov.io/gh/rachmataditiya/odoo-rust-mcp/branch/main/graph/badge.svg)](https://codecov.io/gh/rachmataditiya/odoo-rust-mcp) [![GitHub release](https://img.shields.io/github/v/release/rachmataditiya/odoo-rust-mcp)](https://github.com/rachmataditiya/odoo-rust-mcp/releases) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Homebrew](https://img.shields.io/badge/homebrew-available-orange)](https://rachmataditiya.github.io/homebrew-odoo-rust-mcp/) [![Debian/Ubuntu](https://img.shields.io/badge/apt-available-blue)](https://rachmataditiya.github.io/odoo-rust-mcp/) [![Docker](https://img.shields.io/badge/docker-ghcr.io%2Fodoo--rust--mcp-2496ED?logo=docker&logoColor=white)](https://github.com/rachmataditiya/odoo-rust-mcp/pkgs/container/odoo-rust-mcp) [![Kubernetes](https://img.shields.io/badge/kubernetes-ready-326CE5?logo=kubernetes&logoColor=white)](https://github.com/rachmataditiya/odoo-rust-mcp/tree/main/k8s) [![Helm](https://img.shields.io/badge/helm-chart-0F1689?logo=helm&logoColor=white)](https://github.com/rachmataditiya/odoo-rust-mcp/tree/main/helm) > **Inspired by Peugeot 3008** - Elegant, innovative, and reliable. Just like the Peugeot 3008 SUV combines style with performance, this MCP server combines elegance with powerful Odoo integration capabilities. Rust implementation of an **Odoo MCP server** (Model Context Protocol), supporting: - **Odoo 19+**: JSON-2 External API (`/json/2/...`) with API key authentication - **Odoo < 19**: JSON-RPC (`/jsonrpc`) with username/password authentication ### Features - MCP over **stdio** (Cursor / Claude Desktop style) - MCP over **Streamable HTTP** (Cursor remote transport) - MCP over **SSE** (legacy HTTP+SSE transport) - MCP over **WebSocket** (standalone server; not used by Cursor) - **Web UI for configuration** on port 3008 (tools, prompts, server, instances JSON) - Inspired by Peugeot 3008's elegant design - **Multi-instance** support via `ODOO_INSTANCES` - **Metadata caching** with configurable TTL to reduce Odoo API calls - **Health check endpoint** for monitoring and configuration validation - **MCP Resources** with `odoo://` URI scheme for resource discovery - **New tools**: list models, check access rights, bulk create - Optional cleanup tools gated behind `ODOO_ENABLE_CLEANUP_TOOLS=true` ### Repository layout - `rust-mcp/`: the Rust MCP server implementation (Cargo workspace root) ### Requirements - Rust toolchain (cargo) - Odoo instance: - **Odoo 19+**: Requires External JSON-2 API and API keys - **Odoo < 19**: Works with standard JSON-RPC endpoint (username/password) Notes: For Odoo 19+, access to JSON-2 external API is only available on **Custom** plans. For Odoo < 19, the standard JSON-RPC endpoint is used which is available on all editions. ### Configuration (environment variables) #### Multi-instance (recommended) **Option A: JSON file (recommended for readability)** See `instances.example.json` in the repository root for a complete example configuration. Create `~/.config/odoo-rust-mcp/instances.json`: ```json { "production": { "url": "https://mycompany.example.com", "db": "mycompany", "apiKey": "YOUR_API_KEY" }, "staging": { "url": "https://staging.mycompany.example.com", "apiKey": "YOUR_API_KEY" }, "legacy": { "url": "https://legacy.mycompany.example.com", "db": "legacy_db", "version": "18", "username": "admin", "password": "admin_password" } } ``` Then in `~/.config/odoo-rust-mcp/env`: ```bash ODOO_INSTANCES_JSON=/path/to/instances.json ``` **Option B: Inline JSON (single line)** Set `ODOO_INSTANCES` directly in env file (must be single line): ```bash ODOO_INSTANCES={"production":{"url":"https://mycompany.example.com","apiKey":"xxx"},"staging":{"url":"https://staging.example.com","apiKey":"yyy"}} ``` **Instance configuration fields:** | Field | Required | Description | |-------|----------|-------------| | `url` | Yes | Odoo server URL | | `db` | Odoo < 19 | Database name (required for legacy, optional for 19+) | | `apiKey` | Odoo 19+ | API key for authentication | | `version` | No | Odoo version (e.g., "17", "18"). If < 19, uses username/password | | `username` | Odoo < 19 | Username for JSON-RPC authentication | | `password` | Odoo < 19 | Password for JSON-RPC authentication | Notes: - `db` is optional for Odoo 19+ (only needed when Host header isn't enough to select DB). - `db` is **required** for Odoo < 19 (legacy mode). - `version` determines authentication mode: `< 19` uses username/password, `>= 19` uses API key. - Extra fields in the JSON are ignored. - If an instance omits `apiKey`, the server will fall back to the global `ODOO_API_KEY` (if set). - If an instance omits `username`/`password`, the server will fall back to `ODOO_USERNAME`/`ODOO_PASSWORD`. #### Single-instance (fallback) **Odoo 19+ (API Key):** ```bash export ODOO_URL="https://mycompany.example.com" export ODOO_API_KEY="YOUR_API_KEY" export ODOO_DB="mycompany" # optional ``` **Odoo < 19 (Username/Password):** ```bash export ODOO_URL="https://mycompany.example.com" export ODOO_DB="mycompany" # required for legacy export ODOO_VERSION="18" # triggers legacy mode export ODOO_USERNAME="admin" export ODOO_PASSWORD="your_password" ``` URL convenience: `ODOO_URL` may be given as `localhost:8069` (it will be normalized to `http://localhost:8069`). #### Local example (Odoo 19+) ```bash export ODOO_URL="localhost:8069" export ODOO_DB="v19_pos" export ODOO_API_KEY="YOUR_API_KEY" ``` #### Local example (Odoo 18 or earlier) ```bash export ODOO_URL="localhost:8069" export ODOO_DB="v18_db" export ODOO_VERSION="18" export ODOO_USERNAME="admin" export ODOO_PASSWORD="admin" ``` ### Tools, prompts, and server metadata via JSON (no recompile) This server is **fully declarative**: - **`tools.json`** defines: - tool `name` / `description` - tool `inputSchema` (JSON Schema; must be Cursor-friendly) - tool `op` (maps tool calls to a primitive operation executed by Rust) - optional `guards` (e.g. `requiresEnvTrue`) - **`prompts.json`** defines prompt `name` / `description` / `content` - **`server.json`** defines initialize metadata (`serverName`, `instructions`, default protocol version) Cursor schema constraints: - Avoid `anyOf`, `oneOf`, `allOf`, `$ref`, `definitions`, and `\"type\": [...]` (type arrays). These are rejected to prevent Cursor schema parsing issues. Auto-reload: - The server watches these files and reloads them on change. - If a JSON file is missing at startup, the server will **create it from built-in seed defaults** (embedded from `rust-mcp/config-defaults/*`). Editable vs seed defaults (important): - **Edit these runtime files** for day-to-day configuration: - `rust-mcp/config/tools.json` - `rust-mcp/config/prompts.json` - `rust-mcp/config/server.json` - **Do not edit** `rust-mcp/config-defaults/*` for normal configuration changes. - Those files are embedded into the binary as seed defaults (used only to auto-create missing runtime config files). - If you change `config-defaults/*`, you must rebuild the binary/image for changes to take effect. Environment variables (optional overrides): ```bash export MCP_TOOLS_JSON="config/tools.json" export MCP_PROMPTS_JSON="config/prompts.json" export MCP_SERVER_JSON="config/server.json" ``` **Automatic Configuration Path Setup:** For new installations (Homebrew, APT, direct binary), the following environment variables are automatically set in your `.env` file: ```bash # MCP Config paths (using user config directory) MCP_TOOLS_JSON=/Users/username/.config/odoo-rust-mcp/tools.json MCP_PROMPTS_JSON=/Users/username/.config/odoo-rust-mcp/prompts.json MCP_SERVER_JSON=/Users/username/.config/odoo-rust-mcp/server.json ``` For existing users upgrading, these variables are automatically added to your `.env` file during the first run after upgrade. Default config files (`tools.json`, `prompts.json`, `server.json`) are also automatically copied to your config directory if they don't exist. Sample files are provided in: - `rust-mcp/config/tools.json` - `rust-mcp/config/prompts.json` - `rust-mcp/config/server.json` Seed defaults (used only when files are missing): - `rust-mcp/config-defaults/tools.json` - `rust-mcp/config-defaults/prompts.json` - `rust-mcp/config-defaults/server.json` #### Disabling Tools To disable specific tools (e.g., create and update operations for read-only access), you have two options: **Option 1: Remove tools from the array (recommended)** Simply remove the tool entries from the `tools` array in `tools.json`. For example, to disable `odoo_create` and `odoo_update`: ```json { "tools": [ { "name": "odoo_search", ... }, { "name": "odoo_search_read", ... }, { "name": "odoo_read", ... } // odoo_create and odoo_update are removed - they won't be available ] } ``` **Option 2: Use guards for conditional disabling** Use `guards` with `requiresEnvTrue` to conditionally enable/disable tools based on environment variables: ```json { "tools": [ { "name": "odoo_create", "description": "Create a new Odoo record...", "guards": { "requiresEnvTrue": "ODOO_ENABLE_WRITE_OPERATIONS" }, "inputSchema": { ... }, "op": { ... } }, { "name": "odoo_update", "description": "Update existing Odoo records...", "guards": { "requiresEnvTrue": "ODOO_ENABLE_WRITE_OPERATIONS" }, "inputSchema": { ... }, "op": { ... } } ] } ``` Then set the environment variable to enable: ```bash export ODOO_ENABLE_WRITE_OPERATIONS=true ``` **Example: Read-only configuration** Here's a minimal `tools.json` for read-only access (no create, update, delete, or batch operations): ```json { "tools": [ { "name": "odoo_search", "description": "Search for Odoo records...", "inputSchema": { ... }, "op": { "type": "search", "map": { ... } } }, { "name": "odoo_search_read", "description": "Search and read Odoo records...", "inputSchema": { ... }, "op": { "type": "search_read", "map": { ... } } }, { "name": "odoo_read", "description": "Read specific Odoo records...", "inputSchema": { ... }, "op": { "type": "read", "map": { ... } } }, { "name": "odoo_count", "description": "Count records matching domain...", "inputSchema": { ... }, "op": { "type": "search_count", "map": { ... } } }, { "name": "odoo_get_model_metadata", "description": "Get model metadata...", "inputSchema": { ... }, "op": { "type": "get_model_metadata", "map": { ... } } }, { "name": "odoo_list_models", "description": "List available Odoo models...", "inputSchema": { ... }, "op": { "type": "list_models", "map": { ... } } } // Note: odoo_create, odoo_update, odoo_delete, odoo_create_batch are excluded ] } ``` After modifying `tools.json`, the server will automatically reload the configuration (no restart needed). ### Advanced Features #### Metadata Caching The server caches Odoo model metadata to reduce repeated API calls. Configure cache TTL: ```bash export ODOO_METADATA_CACHE_TTL_SECS=3600 # default: 1 hour ``` Cache is in-memory and shared across all instances. Useful for reducing latency when working with large models or frequent metadata queries. #### Health Check Endpoint When running in HTTP mode, a health check endpoint is available: ```bash GET /health ``` Response: ```json { "status": "ok", "version": "0.3.0", "instance": { "name": "default", "reachable": true } } ``` #### Configuration Validation Validate your Odoo instance configuration before starting the server: ```bash ./rust-mcp validate-config ``` This command tests connectivity to all configured instances and reports any authentication or connection issues. #### MCP Resources The server exposes Odoo resources via the MCP Resources protocol using `odoo://` URIs: - `odoo://instances` - List configured Odoo instances - `odoo://{instance}/models` - List accessible models in an instance - `odoo://{instance}/metadata/{model}` - Get field metadata for a model MCP clients that support resources can use these to discover available Odoo models and fields dynamically. #### Configuration Server (Web UI) A web-based configuration interface is available on port **3008** for managing `tools.json`, `prompts.json`, `server.json`, and `instances.json` without manual file editing. The port number is inspired by the Peugeot 3008 - a symbol of elegance, innovation, and reliability. **Access the config UI:** - Local: `http://localhost:3008` - Docker Compose: `http://localhost:3008` or via Traefik `http://mcp-config.localhost` - Kubernetes: Exposed via service on port 3008 **Features:** - **Authentication**: Secure login with username/password (configurable via `.env`) - **Edit all JSON configuration files** via interactive UI - **Real-time JSON validation** before saving with automatic rollback on errors - **Error handling**: Automatic backup and restore if configuration becomes corrupted - **MCP HTTP Auth Management**: Enable/disable authentication and generate tokens directly from UI - **Config UI Credentials Management**: Change login username and password from the UI - **Tool enable/disable checkboxes** (e.g., toggle `ODOO_ENABLE_CLEANUP_TOOLS`) - **Automatic file watching and hot reload** (no server restart needed) - **User notifications**: Success, error, and warning messages displayed in the UI - **REST API endpoints** for programmatic access: - `GET /api/config/{instances|tools|prompts|server}` - Retrieve config - `POST /api/config/{instances|tools|prompts|server}` - Update config - `GET /api/config/auth/status` - Get MCP auth status - `POST /api/config/auth/enable` - Enable/disable MCP HTTP auth - `POST /api/config/auth/token/generate` - Generate new MCP auth token - `POST /api/config/auth/credentials` - Update Config UI credentials - `GET /health` - Health check **Environment variables:** ```bash # Config server port export ODOO_CONFIG_SERVER_PORT=3008 # default: 3008 (inspired by Peugeot 3008) # Config UI Authentication (default credentials) export CONFIG_UI_USERNAME=admin # default: admin export CONFIG_UI_PASSWORD=changeme # default: changeme (CHANGE THIS!) # MCP HTTP Transport Authentication export MCP_AUTH_ENABLED=false # default: false export MCP_AUTH_TOKEN= # Bearer token (generate: openssl rand -hex 32) ``` **First-time setup:** 1. Open `http://localhost:3008` 2. Login with default credentials (`admin` / `changeme`) 3. **IMPORTANT**: Go to **Security** tab and change the default password immediately 4. Configure your Odoo instances in the **Instances** tab 5. Optionally enable MCP HTTP authentication from the **Security** tab **Example: Enable/disable cleanup tools via UI** 1. Open `http://localhost:3008` and login 2. Go to **Tools** tab 3. Find the `odoo_create_batch` tool (or other cleanup operations) 4. Toggle the **ODOO_ENABLE_CLEANUP_TOOLS** checkbox 5. Save changes (file is auto-reloaded) **Example: Enable MCP HTTP Authentication** 1. Open `http://localhost:3008` and login 2. Go to **Security** tab 3. Toggle **Enable MCP HTTP Authentication** 4. Click **Generate New Token** (or use existing token) 5. Changes apply immediately (hot-reload, no restart needed) 6. Use the token in your MCP client configuration: ```json { "mcpServers": { "odoo-rust-mcp": { "url": "http://127.0.0.1:8787/mcp", "headers": { "Authorization": "Bearer your-generated-token-here" } } } } ``` **Security Notes:** - Config UI credentials are stored in `.env` file (not in JSON configs) - MCP auth settings are also stored in `.env` and can be hot-reloaded - All configuration changes are validated before saving - Automatic backup and rollback prevent configuration corruption - For production, use strong passwords and secure tokens ### Installation #### Option 1: Homebrew (macOS/Linux) - Recommended ```bash # Install brew tap rachmataditiya/odoo-rust-mcp brew trust rachmataditiya/odoo-rust-mcp # If prompted on Homebrew 4.4+ brew install rust-mcp # Configure (edit with your Odoo credentials) nano ~/.config/odoo-rust-mcp/env # Start as background service brew services start rust-mcp ``` Or install directly in one command: ```bash brew install rachmataditiya/odoo-rust-mcp/rust-mcp ``` **What gets installed:** | Component | Location | |-----------|----------| | Binary | `/opt/homebrew/bin/rust-mcp` | | Service wrapper | `/opt/homebrew/bin/rust-mcp-service` | | User configs | `~/.config/odoo-rust-mcp/` (auto-created) | | Service logs | `/opt/homebrew/var/log/rust-mcp.log` | **User config directory (`~/.config/odoo-rust-mcp/`):** ``` ├── env # Environment variables - EDIT THIS with Odoo credentials ├── tools.json # MCP tools definition ├── prompts.json # MCP prompts definition └── server.json # MCP server metadata ``` **Service commands:** ```bash brew services start rust-mcp # Start service brew services stop rust-mcp # Stop service brew services restart rust-mcp # Restart after config changes brew services list # Check status tail -f /opt/homebrew/var/log/rust-mcp.log # View logs ``` Service endpoint: `http://127.0.0.1:8787/mcp` **For Cursor/Claude Desktop/Windsurf with Homebrew:** The binary automatically loads config from `~/.config/odoo-rust-mcp/env`, so you can use it directly: ```json { "mcpServers": { "odoo": { "command": "/opt/homebrew/bin/rust-mcp", "args": ["--transport", "stdio"] } } } ``` **Note:** Starting from v0.2.4, the binary (`rust-mcp`) automatically: - Creates `~/.config/odoo-rust-mcp/` directory if it doesn't exist - Creates a default `env` template file with multi-instance configuration - Loads environment variables from `~/.config/odoo-rust-mcp/env` - Sets default MCP config paths (`MCP_TOOLS_JSON`, `MCP_PROMPTS_JSON`, `MCP_SERVER_JSON`) pointing to user config directory - Copies default config files from Homebrew share directory if they don't exist - Migrates existing single-instance configurations to `instances.json` format - Adds missing environment variables (like `CONFIG_UI_USERNAME`, `CONFIG_UI_PASSWORD`, `MCP_AUTH_ENABLED`) to existing `.env` files This means you can use `rust-mcp` directly without the shell wrapper `rust-mcp-service`. This is especially important for MCP clients like Windsurf that don't support shell script execution. For full Homebrew documentation, see: https://rachmataditiya.github.io/homebrew-odoo-rust-mcp/ #### Agent Skills & Intelligence (Claude Code & Antigravity CLI) Teach Claude Code, Antigravity CLI (AGY), or Cursor to act as an autonomous Odoo ERP expert with domain knowledge, safe multi-instance protocols, Polish notation domain guides, and standard workflow runbooks: To install the agent skill and register the MCP server automatically: ```bash ./scripts/install-agent-skills.sh ``` Or configure manually: **Claude Code (`~/.claude.json`):** ```json { "mcpServers": { "odoo": { "type": "http", "url": "http://127.0.0.1:8787/mcp" } } } ``` **Antigravity CLI (`~/.gemini/config/mcp_config.json`):** ```json { "mcpServers": { "odoo": { "url": "http://127.0.0.1:8787/mcp" } } } ``` Skill documentation and runbooks are located at `skills/odoo-mcp/SKILL.md`. #### Option 2: APT (Debian/Ubuntu) ```bash # Add GPG key curl -fsSL https://rachmataditiya.github.io/odoo-rust-mcp/pubkey.gpg | sudo gpg --dearmor -o /usr/share/keyrings/rust-mcp.gpg # Add repository echo "deb [signed-by=/usr/share/keyrings/rust-mcp.gpg] https://rachmataditiya.github.io/odoo-rust-mcp stable main" | sudo tee /etc/apt/sources.list.d/rust-mcp.list # Install sudo apt update sudo apt install rust-mcp # Configure (edit with your Odoo credentials) nano ~/.config/rust-mcp/env # Start service sudo systemctl start rust-mcp sudo systemctl enable rust-mcp ``` **What gets installed:** | Component | Location | |-----------|----------| | Binary | `/usr/bin/rust-mcp` | | Service wrapper | `/usr/bin/rust-mcp-service` | | Default configs | `/usr/share/rust-mcp/` | | User configs | `~/.config/rust-mcp/` (auto-created on install) | | Systemd service | `/lib/systemd/system/rust-mcp.service` | **Service commands:** ```bash sudo systemctl start rust-mcp # Start service sudo systemctl stop rust-mcp # Stop service sudo systemctl restart rust-mcp # Restart after config changes sudo systemctl status rust-mcp # Check status journalctl -u rust-mcp -f # View logs ``` Service endpoint: `http://127.0.0.1:8787/mcp` #### Option 3: Download and install Download the latest release for your platform from [GitHub Releases](https://github.com/rachmataditiya/odoo-rust-mcp/releases): | Platform | File | |----------|------| | Linux x86_64 | `rust-mcp-x86_64-unknown-linux-gnu.tar.gz` | | macOS x86_64 | `rust-mcp-x86_64-apple-darwin.tar.gz` | | macOS ARM64 (Apple Silicon) | `rust-mcp-aarch64-apple-darwin.tar.gz` | | Windows x86_64 | `rust-mcp-x86_64-pc-windows-msvc.zip` | Extract and install: **Linux / macOS:** ```bash tar -xzf rust-mcp-.tar.gz cd rust-mcp- ./install.sh ``` This installs: - Binary to `/usr/local/bin/rust-mcp` - Config files to `/usr/local/share/odoo-rust-mcp/` To uninstall: ```bash ./install.sh uninstall ``` **Windows (PowerShell as Administrator):** ```powershell Expand-Archive rust-mcp-x86_64-pc-windows-msvc.zip -DestinationPath rust-mcp cd rust-mcp .\install.ps1 ``` This installs: - Binary to `C:\Program Files\odoo-rust-mcp\` - Config files to `C:\ProgramData\odoo-rust-mcp\` - Adds binary to system PATH To uninstall: ```powershell .\install.ps1 -Uninstall ``` **Manual (without installer):** ```bash # Linux/macOS - just run directly tar -xzf rust-mcp-.tar.gz ./rust-mcp --transport stdio # Windows Expand-Archive rust-mcp-x86_64-pc-windows-msvc.zip -DestinationPath . .\rust-mcp.exe --transport stdio ``` The release archive includes: - `rust-mcp` (or `rust-mcp.exe` on Windows) - the binary - `config/` - default configuration files (tools.json, prompts.json, server.json) - `.env.example` - example environment variables - `install.sh` (Linux/macOS) or `install.ps1` (Windows) - installer script #### Option 4: Build from source ```bash cd rust-mcp cargo build --release ``` ### Run as Background Service The installer scripts support running the MCP server as a background service (HTTP transport on port 8787). **Linux (systemd):** ```bash ./install.sh service ``` Commands: - Start: `sudo systemctl start odoo-rust-mcp` - Stop: `sudo systemctl stop odoo-rust-mcp` - Status: `sudo systemctl status odoo-rust-mcp` - Logs: `sudo journalctl -u odoo-rust-mcp -f` Config: `/etc/odoo-rust-mcp.env` **macOS (launchd):** ```bash ./install.sh service ``` Commands: - Start: `launchctl load ~/Library/LaunchAgents/com.odoo.rust-mcp.plist` - Stop: `launchctl unload ~/Library/LaunchAgents/com.odoo.rust-mcp.plist` - Logs: `tail -f ~/.config/odoo-rust-mcp/stdout.log` Config: `~/.config/odoo-rust-mcp/env` **Windows (Scheduled Task):** ```powershell .\install.ps1 -Service ``` Commands (PowerShell as Admin): - Start: `Start-ScheduledTask -TaskName OdooRustMcpService` - Stop: `Stop-ScheduledTask -TaskName OdooRustMcpService` - Status: `Get-ScheduledTask -TaskName OdooRustMcpService | Select-Object State` Config: `C:\ProgramData\odoo-rust-mcp\env.ps1` **Service endpoint:** `http://127.0.0.1:8787/mcp` To remove service only: ```bash # Linux/macOS ./install.sh service-uninstall # Windows .\install.ps1 -ServiceUninstall ``` ### Run (stdio) ```bash cd rust-mcp ./target/release/rust-mcp --transport stdio ``` ### Run (Streamable HTTP) ```bash cd rust-mcp ./target/release/rust-mcp --transport http --listen 127.0.0.1:8787 ``` Endpoints: - Streamable HTTP: `http://127.0.0.1:8787/mcp` - Legacy SSE: `http://127.0.0.1:8787/sse` (paired with `POST /messages`) - Health check: `http://127.0.0.1:8787/health` (no auth required) - OpenAPI spec: `http://127.0.0.1:8787/openapi.json` (no auth required) ### Streamable HTTP Transport (MCP 2025-11-25) The HTTP transport implements the **MCP Streamable HTTP specification** (version 2025-11-25), which provides efficient real-time communication using Server-Sent Events (SSE). **Endpoints:** | Method | Path | Description | |--------|------|-------------| | POST | `/mcp` | Send JSON-RPC requests/notifications | | GET | `/mcp` | Open SSE stream for server-to-client messages | | DELETE | `/mcp` | Explicitly terminate a session | **Features:** - **Session Management**: Sessions are created on `initialize` and tracked via `MCP-Session-Id` header - **SSE Streaming**: Server can push notifications and progress updates via GET `/mcp` - **Event IDs**: Each SSE event has a unique ID for resumability support - **Protocol Version Header**: `MCP-Protocol-Version` header for version negotiation - **Origin Validation**: Security protection against DNS rebinding attacks **Headers:** | Header | Description | |--------|-------------| | `MCP-Session-Id` | Session identifier (returned on initialize, required for subsequent requests) | | `MCP-Protocol-Version` | Protocol version (e.g., `2025-03-26`, `2025-11-05`) | | `Authorization` | Bearer token authentication (if `MCP_AUTH_TOKEN` is set) | | `Last-Event-ID` | Resume SSE stream from a specific event (for reconnection) | **Example: Initialize session** ```bash curl -X POST http://127.0.0.1:8787/mcp \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26"}}' ``` Response includes `MCP-Session-Id` header for subsequent requests. **Example: Open SSE stream** ```bash curl -N http://127.0.0.1:8787/mcp \ -H "Accept: text/event-stream" \ -H "MCP-Session-Id: " ``` **Example: Terminate session** ```bash curl -X DELETE http://127.0.0.1:8787/mcp \ -H "MCP-Session-Id: " ``` **Security Configuration:** ```bash # Enable Origin validation (comma-separated list of allowed origins) export MCP_ALLOWED_ORIGINS="https://example.com,https://app.example.com" # Localhost-only mode (empty value) export MCP_ALLOWED_ORIGINS="" # Disable Origin validation (default - not recommended for production) # Simply don't set MCP_ALLOWED_ORIGINS ``` When Origin validation is enabled: - Requests with invalid Origin header receive `403 Forbidden` - Localhost origins (`localhost`, `127.0.0.1`, `[::1]`) are always allowed - Requests without Origin header (same-origin or non-browser clients) are allowed ### HTTP API Documentation The HTTP API is documented using OpenAPI 3.0 specification. You can: - View the OpenAPI spec: `http://localhost:8787/openapi.json` - Use the spec with tools like Swagger UI, Postman, or n8n for integration - The spec documents all endpoints including request/response schemas For integration with tools like n8n or Dify, refer to the OpenAPI specification for complete API details. ### Authentication (HTTP Transport) The HTTP transport supports Bearer token authentication as per the [MCP specification](https://modelcontextprotocol.io/specification/draft/basic/authorization). **Enable authentication via environment variables:** ```bash export MCP_AUTH_ENABLED=true export MCP_AUTH_TOKEN=your-secure-random-token-here ``` **Or enable via Config UI (recommended):** 1. Open `http://localhost:3008` and login 2. Go to **Security** tab 3. Toggle **Enable MCP HTTP Authentication** 4. Click **Generate New Token** or paste your existing token 5. Changes apply immediately (hot-reload, no restart needed) When `MCP_AUTH_ENABLED=true` and `MCP_AUTH_TOKEN` is set, all HTTP requests must include the `Authorization` header: ``` Authorization: Bearer your-secure-random-token-here ``` **Example with curl:** ```bash curl -X POST http://127.0.0.1:8787/mcp \ -H "Content-Type: application/json" \ -H "Authorization: Bearer your-secure-random-token-here" \ -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05"}}' ``` **Hot-Reload Support:** - Authentication settings can be enabled/disabled and tokens can be regenerated from the Config UI - Changes take effect immediately without restarting the service - The server uses `Arc` for thread-safe, dynamic configuration updates **Notes:** - If `MCP_AUTH_ENABLED=false` or `MCP_AUTH_TOKEN` is not set, authentication is disabled (not recommended for production) - STDIO transport does not use HTTP authentication (credentials come from environment) - Generate a secure token: `openssl rand -hex 32` - For production deployments, always enable authentication ### Run (WebSocket / standalone server) ```bash cd rust-mcp ./target/release/rust-mcp --transport ws --listen 127.0.0.1:8787 ``` ### Run with Docker Compose Create `.env` in the repo root (example in `dotenv.example`), then: ```bash docker compose up --build ``` By default, the container runs **HTTP** transport and exposes `http://localhost:8787/mcp`. **Features:** - Custom network `mcp-network` for integration with other containers (n8n, dify, etc.) - Health check for container orchestration - Volume mounts for config files - Support for `ODOO_INSTANCES_JSON` for multi-instance setup - Resource limits and labels for service discovery **Multi-instance configuration:** 1. Create `instances.json` with your Odoo instances: ```json { "production": {"url": "https://odoo.example.com", "db": "prod", "apiKey": "xxx"}, "staging": {"url": "https://staging.example.com", "db": "staging", "apiKey": "yyy"} } ``` 2. Mount and configure in `.env`: ```bash ODOO_INSTANCES_JSON=/config/instances.json ``` 3. Uncomment the volume mount in `docker-compose.yml`: ```yaml volumes: - ./instances.json:/config/instances.json:ro ``` **Integration with other containers:** The MCP server runs on the `mcp-network` network. Other containers can connect using: ```yaml # In your other service's docker-compose.yml services: n8n: networks: - mcp-network environment: MCP_URL: "http://odoo-mcp:8787/mcp" networks: mcp-network: external: true ``` See `docker-compose.override.example.yml` for more integration examples. ### Run with Kubernetes The project includes Kubernetes manifests in `k8s/` for production deployments. **Quick start with raw manifests:** ```bash # Apply all manifests kubectl apply -k k8s/ # Or apply individually kubectl apply -f k8s/namespace.yaml kubectl apply -f k8s/configmap.yaml kubectl apply -f k8s/secret.yaml # Edit first with your credentials! kubectl apply -f k8s/deployment.yaml kubectl apply -f k8s/service.yaml kubectl apply -f k8s/ingress.yaml # Optional, for external access ``` **Verify deployment:** ```bash kubectl -n odoo-mcp get pods kubectl -n odoo-mcp logs -f deployment/odoo-mcp ``` **Port-forward for local testing:** ```bash kubectl -n odoo-mcp port-forward svc/odoo-mcp 8787:8787 # Access: http://127.0.0.1:8787/mcp ``` **Features:** - Namespace isolation - ConfigMap for MCP configuration files - Secret for Odoo credentials - Deployment with: - Resource limits/requests - Liveness/readiness/startup probes - Pod anti-affinity for high availability - Non-root security context - ClusterIP Service for internal access - Ingress for external access with TLS - Kustomization for environment management ### Run with Helm For more flexible deployments, use the Helm chart in `helm/odoo-rust-mcp/`. **Install:** ```bash # Add your values (see helm/odoo-rust-mcp/values.yaml for all options) cat > my-values.yaml <", 0]] } ``` Decoded result (shape): ```json { "count": 18 } ``` Execute action/button (workflow): ```json { "instance": "default", "model": "sale.order", "ids": [42], "action": "action_confirm" } ``` Decoded result (shape): ```json { "result": null, "executed_on": [42] } ``` Execute arbitrary method: ```json { "instance": "default", "model": "res.partner", "method": "name_get", "args": [[1, 2, 3]] } ``` Decoded result (shape): ```json { "result": [] } ``` Model metadata (fields/types): ```json { "instance": "default", "model": "sale.order" } ``` Decoded result (shape): ```json { "model": { "name": "sale.order", "description": "Sales Order", "fields": {} } } ``` Generate report (PDF as base64): ```json { "instance": "default", "reportName": "sale.report_saleorder", "ids": [42] } ``` Decoded result (shape): ```json { "pdf_base64": "JVBERi0xLjQKJ......", "report_name": "sale.report_saleorder", "record_ids": [42] } ``` List models (with optional filtering): ```json { "instance": "default", "domain": [["transient", "=", false]], "limit": 50, "offset": 0 } ``` Decoded result (shape): ```json { "models": [ {"id": 2, "model": "ir.actions", "name": "Actions"}, {"id": 3, "model": "ir.model", "name": "Models"} ] } ``` Check access rights: ```json { "instance": "default", "model": "res.partner", "operation": "write", "ids": [1, 2, 3] } ``` Decoded result (shape): ```json { "has_access": true, "model": "res.partner", "operation": "write", "model_level": true, "record_level": true } ``` Bulk create records (max 100): ```json { "instance": "default", "model": "res.partner", "values_list": [ {"name": "Partner 1", "email": "p1@example.com"}, {"name": "Partner 2", "email": "p2@example.com"} ] } ``` Decoded result (shape): ```json { "ids": [101, 102], "created_count": 2, "success": true } ``` ### Claude Desktop config example Set your MCP server command to the built binary: **Odoo 19+ (API Key):** ```json { "mcpServers": { "odoo-rust": { "command": "/absolute/path/to/odoo-rust-mcp/rust-mcp/target/release/rust-mcp", "args": ["--transport", "stdio"], "env": { "ODOO_INSTANCES": "{\"production\":{\"url\":\"https://mycompany.example.com\",\"db\":\"mycompany\",\"apiKey\":\"YOUR_API_KEY\"}}" } } } } ``` **Odoo < 19 (Username/Password):** ```json { "mcpServers": { "odoo-rust": { "command": "/absolute/path/to/odoo-rust-mcp/rust-mcp/target/release/rust-mcp", "args": ["--transport", "stdio"], "env": { "ODOO_INSTANCES": "{\"legacy\":{\"url\":\"https://mycompany.example.com\",\"db\":\"mycompany\",\"version\":\"18\",\"username\":\"admin\",\"password\":\"your_password\"}}" } } } } ``` ### Cursor config example Cursor supports `stdio`, `SSE`, and `Streamable HTTP` transports. See Cursor docs: [`cursor.com/docs/context/mcp`](https://cursor.com/docs/context/mcp). #### Cursor (recommended): stdio Put this in `~/.cursor/mcp.json` (or `${workspaceFolder}/.cursor/mcp.json`): **IMPORTANT:** For stdio transport, you must use **absolute paths** for config files because Cursor runs the binary from a different working directory. **Odoo 19+ (complete example):** ```json { "mcpServers": { "odoo-rust-mcp": { "type": "stdio", "command": "/absolute/path/to/odoo-rust-mcp/rust-mcp/target/release/rust-mcp", "args": ["--transport", "stdio"], "env": { "ODOO_URL": "http://localhost:8069", "ODOO_DB": "mydb", "ODOO_API_KEY": "YOUR_API_KEY", "MCP_TOOLS_JSON": "/absolute/path/to/odoo-rust-mcp/config/tools.json", "MCP_PROMPTS_JSON": "/absolute/path/to/odoo-rust-mcp/config/prompts.json", "MCP_SERVER_JSON": "/absolute/path/to/odoo-rust-mcp/config/server.json" } } } } ``` **Odoo < 19 (complete example):** ```json { "mcpServers": { "odoo-rust-mcp": { "type": "stdio", "command": "/absolute/path/to/odoo-rust-mcp/rust-mcp/target/release/rust-mcp", "args": ["--transport", "stdio"], "env": { "ODOO_URL": "http://localhost:8069", "ODOO_DB": "mydb", "ODOO_VERSION": "18", "ODOO_USERNAME": "admin", "ODOO_PASSWORD": "admin", "MCP_TOOLS_JSON": "/absolute/path/to/odoo-rust-mcp/config/tools.json", "MCP_PROMPTS_JSON": "/absolute/path/to/odoo-rust-mcp/config/prompts.json", "MCP_SERVER_JSON": "/absolute/path/to/odoo-rust-mcp/config/server.json" } } } } ``` **Using .env file:** ```json { "mcpServers": { "odoo-rust-mcp": { "type": "stdio", "command": "/absolute/path/to/odoo-rust-mcp/rust-mcp/target/release/rust-mcp", "args": ["--transport", "stdio"], "envFile": "/absolute/path/to/odoo-rust-mcp/.env" } } } ``` Note: When using `envFile`, ensure your `.env` contains absolute paths for `MCP_TOOLS_JSON`, `MCP_PROMPTS_JSON`, and `MCP_SERVER_JSON`. #### Cursor: Streamable HTTP (remote / multi-user) Run the server with `--transport http` and set: ```json { "mcpServers": { "odoo-rust-mcp": { "url": "http://127.0.0.1:8787/mcp" } } } ``` **With Bearer Token Authentication:** If you have `MCP_AUTH_TOKEN` set on the server, configure Cursor with the token in headers: ```json { "mcpServers": { "odoo-rust-mcp": { "url": "http://127.0.0.1:8787/mcp", "headers": { "Authorization": "Bearer your-secure-random-token-here" } } } } ``` Note: Generate a secure token with `openssl rand -hex 32` and set `MCP_AUTH_TOKEN` on the server. ### Test / smoke Run unit tests (no warnings): ```bash cd rust-mcp RUSTFLAGS='-Dwarnings' cargo test ``` There is also a small WS smoke client that can validate end-to-end MCP calls (connect → initialize → list tools → call a few tools): ```bash cd rust-mcp cargo run --release --bin ws_smoke_client -- \ --url ws://127.0.0.1:8787 \ --instance default \ --model res.partner ``` Example output (local run against `res.partner`): ```text tools/list: 11 tools - odoo_search - odoo_search_read - odoo_read - odoo_create - odoo_update - odoo_delete - odoo_execute - odoo_count - odoo_workflow_action - odoo_generate_report - odoo_get_model_metadata odoo_count result: {"count":18} odoo_search_read count: 2 odoo_search_read sample records: [{"id":46,"name":"Kasir C"},{"id":45,"name":"Kasir B"}] prompts/list: odoo_common_models, odoo_domain_filters ``` ### Security - Do **not** commit `.env` or any file containing API keys/passwords. - Prefer using dedicated bot users with minimal access rights in Odoo for automation. ## Release Process Untuk membuat release baru, ada **2 file yang harus di-update versinya**: 1. `rust-mcp/Cargo.toml` - field `version` 2. `config-ui/package.json` - field `version` ### Quick Release (Recommended) Gunakan script helper yang sudah disediakan: ```bash # Bump version, commit, push, dan create tag sekaligus ./scripts/release.sh 0.3.15 ``` Script ini akan: - Update version di `Cargo.toml` dan `package.json` - Commit perubahan - Push ke remote - Create dan push git tag (yang akan trigger GitHub Actions release workflow) ### Manual Release Jika ingin manual: ```bash # 1. Update version di 2 file # Edit rust-mcp/Cargo.toml: version = "0.3.15" # Edit config-ui/package.json: "version": "0.3.15" # 2. Commit dan push git add rust-mcp/Cargo.toml config-ui/package.json git commit -m "chore: bump version to 0.3.15" git push # 3. Create dan push tag (akan trigger GitHub Actions) git tag v0.3.15 git push origin v0.3.15 ``` ### GitHub Actions akan otomatis: - Build binaries untuk semua platform (Linux, macOS, Windows) - Build Docker image - Build Debian package - Create GitHub release dengan artifacts - Update Homebrew formula - Update APT repository Monitor progress: https://github.com/rachmataditiya/odoo-rust-mcp/actions