ocip-odoo-mcp
v1.1.1
Published
Multi-tenant Odoo JSON-RPC middleware with dynamic tool registry and REST API
Maintainers
Readme
odoo-mcp
Published on npm as ocip-odoo-mcp. (The name odoo-mcp is already taken; the @ocip scope requires an npm org, which caused “Scope not found” until this name was used.)
Multi-tenant middleware on Odoo’s JSON-RPC (common.authenticate, object.execute_kw):
- HTTP API (Express):
POST /execute,GET /tools, etc. - Cursor / Claude MCP (stdio): same tool logic over the Model Context Protocol; logs go to stderr only so stdout stays valid JSON-RPC.
Cursor MCP (mcp.json)
When the process is started with stdin not a TTY (how Cursor runs npx), the app uses MCP stdio automatically — no extra flags.
{
"mcpServers": {
"odoo": {
"command": "npx",
"args": ["-y", "ocip-odoo-mcp@latest"],
"env": {
"ODOO_COMPANIES_FILE": "/absolute/path/to/companies.json"
}
}
}
}You can also pass ODOO_COMPANIES as a JSON string (same shape as the REST config). Every MCP tool takes a company argument matching the tenant id.
To force a mode: --stdio / --http, or MCP_TRANSPORT=stdio|http, or ODOO_MCP_MODE=stdio|http. For HTTP in Docker (no TTY), set MCP_TRANSPORT=http or ODOO_MCP_MODE=http.
Requirements
- Node.js ≥ 18
- Network access to your Odoo instance’s
/jsonrpcendpoint - Odoo credentials (password or API key) per tenant
Quick start
cd odoo-mcp
cp .env.example .env
cp companies.example.json companies.json
# Edit companies.json with real url, database, username, passwordPoint the server at your companies file:
# In .env
ODOO_COMPANIES_FILE=./companies.jsonOr inline JSON (harder to maintain in .env):
ODOO_COMPANIES=[{"id":"company_a","url":"https://odoo.example.com","database":"db","username":"user","password":"secret"}]Start:
npm install
npm startDefault port: 3840 (override with PORT).
Configuration
| Variable | Description |
|----------|-------------|
| PORT | HTTP port (default 3840) |
| NODE_ENV | e.g. development / production |
| API_BEARER_TOKEN | If set, Authorization: Bearer <token> required for /tools and /execute |
| ODOO_COMPANIES | JSON array of tenant objects (see below) |
| ODOO_COMPANIES_FILE | Path to a JSON file with the same array shape |
| ODOO_RPC_TIMEOUT_MS | Axios timeout for JSON-RPC (default 60000) |
| ODOO_SESSION_REFRESH_MS | Re-login after this many ms (0 = only on failure / retry) |
| TOOL_CACHE_TTL_SECONDS | TTL for get_model_data when use_cache: true |
| LOG_LEVEL | error | warn | info | debug |
Tenant object shape
Each entry in the companies array:
| Field | Description |
|-------|-------------|
| id | Sent as company in API requests |
| url | Odoo base URL (no trailing slash), e.g. https://odoo.example.com |
| database | Odoo database name |
| username | Login or API user |
| password | Password or Odoo API key |
Do not commit real credentials; use .env and companies.json locally and keep them out of git (see .gitignore).
HTTP API
GET /health
Liveness and tenant count. No bearer token required (even when API_BEARER_TOKEN is set).
GET /tools
Lists registered tools with name, description, and parameter schema.
POST /execute
Runs a tool for a tenant.
Body:
{
"company": "company_a",
"tool": "get_model_data",
"params": {
"model": "res.partner",
"fields": ["name", "email"],
"domain": [],
"limit": 10
}
}Success:
{
"ok": true,
"company": "company_a",
"tool": "get_model_data",
"data": [ ... ]
}Error:
{
"ok": false,
"error": {
"code": "UNKNOWN_COMPANY",
"message": "...",
"details": { }
}
}More examples: examples/api-requests.http.
Built-in tools
| Tool | Purpose |
|------|---------|
| verify_session | Authenticate (or reuse cache); returns uid and database |
| get_model_data | search_read with optional domain, fields, limit, offset, order; optional use_cache |
| odoo_search | search (ids) |
| create_record | create |
| update_record | write |
| odoo_unlink | unlink |
| execute_kw | Generic execute_kw (use with care) |
| list_models | Introspection via ir.model |
| get_model_fields | Introspection via ir.model.fields |
Adding tools
Implement a tool definition and register it in src/tools/index.js, or import ToolRegistry from the same module and build a custom registry. Each tool needs name, description, parametersSchema, and execute(ctx, params) where ctx includes connectionManager and companyId.
Project layout
src/
index.js # Express app entry
config/ # Env + company loading
services/
odooClient.js # JSON-RPC client
connectionManager.js # Session cache + execute_kw retry
tools/ # Registry + built-in tools
routes/ # HTTP routes + error handler
middleware/ # Optional API bearer auth
utils/ # Logger, errors, redaction, simple TTL cacheOperations
- Reload companies without full restart: send SIGHUP to the process; company config reloads and in-memory sessions are cleared.
Security notes
- Passwords and tokens are not logged; request logging redacts common secret fields.
- Prefer API keys and least-privilege Odoo users for automation.
- Use
API_BEARER_TOKENin front of untrusted networks.
Scripts
| Command | Description |
|---------|-------------|
| npm start | Run the server |
| npm run dev | Run with node --watch |
Install from npm
npm install ocip-odoo-mcpRun the server (after configuring env / companies.json in your project or current directory):
npx ocip-odoo-mcp
# or, if installed globally:
odoo-mcpdotenv loads .env from the current working directory, not from inside node_modules, so keep .env and companies.json in the folder from which you start the process.
Publishing
Unscoped packages are public by default; you do not need an npm org.
- Log in:
npm login - From this directory:
npm publishIf you later switch to a scoped name (e.g. @your-org/odoo-mcp), create the org at npmjs.com/org/create and publish with:
npm publish --access publicDry-run the tarball:
npm pack --dry-runLicense
MIT — see LICENSE.
