odoo19-mcp-server
This server provides an MCP (Model Context Protocol) interface to interact with Odoo 19 ERP systems via its JSON-2 API, enabling AI assistants to query and manipulate Odoo data.
Model Discovery & Schema Inspection
list_models: Browse all available Odoo models with optional name filteringget_fields: Inspect field metadata (types, labels, required/readonly flags, relations, etc.) for any model
Data Querying
search_records: Query records using Odoo domain syntax with filtering, sorting, pagination, and Odoo 19+anyoperator supportcount_records: Count records matching a domain without fetching dataread_records: Fetch specific records by ID (includes direct browser URL links)
Data Manipulation
create_record: Create single or multiple records in any modelupdate_record: Update one or more records by IDdelete_record: Delete records via a mandatory two-step confirmation flowexecute_method: Call arbitrary methods on any model (e.g., confirm orders, trigger workflows)
Resources (Dynamic Context)
odoo://user/odoo://company: Current user and company infoodoo://models: All available modelsodoo://model/{model_name}: Field definitions for a modelodoo://record/{model_name}/{record_id}: A single record
Safety & Security
READONLY_MODE=truehides all write tools from the LLM entirely (includingexecute_methodfor write operations)Delete operations require explicit user confirmation
Sensitive fields (binary/image/HTML) are automatically filtered
API key authentication for Odoo access
Deployment & Integration
Supports
stdio(local),http, andssetransport modesDocker/Docker Compose support for cloud deployment
Compatible with Claude Code, Gemini CLI, and MCP Inspector
/healthendpoint for monitoring in HTTP/SSE modes
Enables interaction with Odoo 19 instances via the JSON-2 API, allowing users to search, create, read, update, and delete records across various models, as well as access system metadata, model definitions, and user/company information.
Odoo 19 MCP Server (JSON-2 API)
支援的 MCP Client
Odoo 19 MCP Server,使用 JSON-2 API 連線。
✨ 支援多 user:設定 MCP_MULTIUSER=true 後,每個 client 以自己的 Odoo API key 認證,權限、操作歸屬、審計紀錄都對應真實 user,不再全部掛在同一個服務帳號上,詳見多 user 模式。
本專案基於 Odoo 19 JSON-2 API 完整使用指南 開發。

技術棧
Python: 3.13
FastMCP: >=4.1.0,<5.0.0
odoo-client-lib: 2.0.1 (JSON-2 API)
Related MCP server: odxproxy-mcpserver
架構
flowchart TB
subgraph Client["MCP Client"]
CC[Claude Code]
GC[Gemini CLI]
MI[MCP Inspector]
end
subgraph Server["MCP Server (FastMCP)"]
R[Resources<br/>odoo://models<br/>odoo://user<br/>odoo://company]
T[Tools<br/>search_records<br/>create_record<br/>update_record]
DI[Dependency Injection<br/>get_caller_client]
end
subgraph RPC["OdooJsonRpcClient"]
OL[odoolib<br/>json2/json2s protocol]
end
subgraph Odoo["Odoo Server"]
EP["/jsonrpc endpoint"]
end
Client -->|MCP Protocol<br/>stdio/http/sse| Server
R --> DI
T --> DI
DI --> RPC
RPC -->|HTTP/HTTPS| OdooMCP 核心概念
Resources vs Tools
特性 | Resources | Tools |
用途 | 提供上下文資訊 | 執行操作/動作 |
觸發 | 客戶端控制(如 Claude Code) | LLM 自動決定呼叫 |
參數 | 無(或 URI 參數) | 有(需 LLM 生成) |
類比 | 員工手冊(背景知識) | 工具箱(按需使用) |
HTTP 類比 | GET(讀取) | POST/PUT/DELETE(操作) |
Resources - 動態上下文,LLM 一開始就知道的背景資訊:
odoo://user → "我是誰"
odoo://company → "我在哪間公司"
odoo://models → "有哪些模型可用"Tools - 需要時才呼叫的操作:
search_records(model="res.partner", domain=[...]) → 搜尋
create_record(model="sale.order", values={...}) → 建立為什麼不用 Default Prompt?
方式 | Default Prompt | Resource |
資料來源 | 寫死在程式碼 | 即時從 Odoo 查詢 |
更新時機 | 部署時 | 每次連線時 |
換用戶登入 | 資訊錯誤 | 自動正確 |
# ❌ Default Prompt(寫死)
SYSTEM_PROMPT = "當前用戶: Admin" # 換人登入就錯了
# ✅ Resource(動態)
@mcp.resource("odoo://user")
def get_current_user():
return client.read("res.users", [uid]) # 即時查詢結論:Resource 是「動態的上下文」,不是靜態文字。
參考:MCP Resources | MCP Tools
環境變數
變數 | 說明 | 預設值 |
| Odoo 伺服器 URL |
|
| 資料庫名稱 | - |
| API Key 認證 | - |
| 唯讀模式(禁止寫入操作) |
|
| 設 |
|
| HTTP/SSE 模式的 Bearer Token 認證(未設定=無認證;stdio 不適用),見安全機制 | -(停用) |
| 多 user 模式:每個 client 拿自己的 Odoo API key 當 Bearer token,見安全機制 |
|
| upload token 的 HMAC secret;僅多 worker 部署需要設定(單一程序自動衍生) | - |
建立 .env 檔案:
cp .env.example .env安裝
pip install -r requirements.txt啟動方式
開發模式(MCP Inspector)
fastmcp dev inspector odoo_mcp_server.py傳輸模式(Transport)
本專案支援三種 MCP 傳輸模式:
模式 | 說明 | 適用情境 |
| 標準輸入輸出(預設) | Claude Desktop、Cursor IDE、本機開發 |
| HTTP 協定 | 遠端服務、n8n、Web 應用整合 |
| Server-Sent Events(已棄用) | 向下相容舊版 Client |
stdio vs HTTP/SSE:算力位置
兩種模式的關鍵差異在於「誰來啟動 MCP Server」以及「算力在哪裡執行」:
stdio 模式(本機算力)
┌─────────────────────────────────────┐
│ 你的電腦 💻 │
│ │
│ Claude Desktop ──> MCP Server │
│ (使用本機算力) │
└─────────────────────────────────────┘Client(如 Claude Desktop)啟動 MCP Server 作為子進程
MCP Server 使用你電腦的 CPU/RAM
Server 隨 Client 啟動/關閉
HTTP/SSE 模式(遠端算力)
┌──────────────┐ ┌──────────────────┐
│ 你的電腦 │ │ 雲端 ☁️ │
│ │ │ │
│Claude Desktop│ ──網路──>│ MCP Server │
│ (輕量) │ │ (使用雲端算力) │
└──────────────┘ └──────────────────┘MCP Server 獨立運行在雲端/遠端主機
多個 Client 可同時連線同一個 Server
適合團隊共用、n8n 整合、正式環境
啟動不同模式
# stdio 模式(預設)
python odoo_mcp_server.py
# HTTP 模式
python odoo_mcp_server.py --transport http --host 0.0.0.0 --port 8000
# SSE 模式(已棄用,建議使用 HTTP)
python odoo_mcp_server.py --transport sse --host 0.0.0.0 --port 8000雲端部署(HTTP 模式)
⚠️ 安全提醒:HTTP 模式預設沒有認證——任何連得到該 port 的人都直接繼承
ODOO_API_KEY的完整權限。除非 server 只在受信任的內網使用, 否則請務必設定MCP_AUTH_TOKEN(或改用多 user 模式MCP_MULTIUSER)並搭配 TLS,詳見安全機制
專案提供 docker-compose.example.yml 範本,複製後修改即可使用:
cp .env.example .env # 填入 ODOO_URL / ODOO_DATABASE / ODOO_API_KEY
cp docker-compose.example.yml docker-compose.yml # 依需求調整
docker compose up -d範本內容
volumes:
shared-uploads:
services:
odoo-mcp:
build: .
command: ["python", "odoo_mcp_server.py", "--transport", "http", "--host", "0.0.0.0", "--port", "8000"]
# 對外暴露 port 8000(host 端 client 可直接連 http://localhost:8000/mcp)。
# ⚠️ "8000:8000" 會綁定 0.0.0.0:同網段的所有機器都連得到
# (主機若有公網 IP,就是整個網際網路),且 Docker 發佈的 port 會繞過 ufw 防火牆規則。
# 建議設定 MCP_AUTH_TOKEN(見 environment);只給本機 client 用可改 "127.0.0.1:8000:8000"。
# 若只需 Docker 內網存取(例如 client 也在同一個 compose 裡),可整段移除 ports。
ports:
- "8000:8000"
environment:
- ODOO_URL=${ODOO_URL}
- ODOO_DATABASE=${ODOO_DATABASE}
- ODOO_API_KEY=${ODOO_API_KEY}
- READONLY_MODE=${READONLY_MODE:-false}
# HTTP 模式的 Bearer Token 認證(未設定=無認證,見 README「安全機制」)
- MCP_AUTH_TOKEN=${MCP_AUTH_TOKEN:-}
# 多 user 模式:每個 client 拿自己的 Odoo API key 當 Bearer token,
# 權限/審計歸屬真實 user(見「多 user 模式」)
# - MCP_MULTIUSER=true
# HTTP 模式的 Host 標頭防護(DNS rebinding protection,來自底層 MCP SDK):
# 用非 localhost 的 IP/網域連進來時,預設會被擋下並回 "Invalid host header"。
# ⚠️ 快速測試可先全開(勿用於正式環境):
- FASTMCP_HTTP_ALLOWED_HOSTS=["*"]
volumes:
- shared-uploads:/shared # 圖片傳遞通道;對應 Dockerfile 預建的 /shared/uploads
restart: unless-stopped圖片 / 附件傳遞:
add_attachment的file_path模式會從/shared/uploads/讀檔上傳到 Odoo,避免大量 base64 佔用 LLM output token。client 與 server 跨機器(不共用此 volume)時,改走prepare_upload→/upload把檔案送進UPLOAD_DIR,詳見安全機制。
連不上、回
Invalid host header? 這是底層 MCP SDK 的 DNS rebinding 防護——用非 localhost 的 IP/網域連進來時,Host 標頭不在允許清單內就會被擋。用FASTMCP_HTTP_ALLOWED_HOSTS放行:# 快速測試(⚠️ 對任何 Host 開放,勿用於正式環境) FASTMCP_HTTP_ALLOWED_HOSTS=["*"] # ✅ 正規做法:只列出 client 實際連線的 host(含 port) FASTMCP_HTTP_ALLOWED_HOSTS=["your-server-ip:8000"] # 純 IP 部署 FASTMCP_HTTP_ALLOWED_HOSTS=["mcp.example.com"] # 反向代理/網域(建議搭配 TLS)官方明確警告:使用萬用字元
*會讓 server 對任何來源開放,正式環境請務必列出明確 host。必要時另有FASTMCP_HTTP_ALLOWED_ORIGINS(瀏覽器型 client 的 Origin 白名單)。
# server 有設 MCP_AUTH_TOKEN 時,需帶 Authorization header
claude mcp add --transport http odoo-mcp https://your-cloud-server.com:8000/mcp --header "Authorization: Bearer your_random_token_here"
# 多 user 模式(MCP_MULTIUSER=true):Bearer 改填「自己的 Odoo API key」
claude mcp add --transport http odoo-mcp https://your-cloud-server.com:8000/mcp --header "Authorization: Bearer <你的 Odoo API key>"
# server 未啟用認證(僅限受信任內網)
claude mcp add --transport http odoo-mcp https://your-cloud-server.com:8000/mcp{
"mcpServers": {
"odoo-mcp": {
"type": "http",
"url": "https://your-cloud-server.com:8000/mcp",
"headers": {
"Authorization": "Bearer your_random_token_here"
}
}
}
}⚠️ 純 HTTP 下 Bearer token 是明文傳輸,僅適合受信任內網/臨時測試;對外請改用
https://(TLS)。
server 未啟用 MCP_AUTH_TOKEN、也未開多 user 模式時,headers 整段可省略。
多 user 模式(MCP_MULTIUSER=true)下 headers 必填,Bearer 改填自己的 Odoo API key,見多 user 模式。
MCP Resources
URI | 說明 |
| 列出所有模型 |
| 取得模型欄位定義 |
| 取得單筆記錄 |
| 當前登入用戶資訊 |
| 當前用戶所屬公司資訊 |
MCP Tools
Tool | 說明 | 唯讀 |
| 列出/搜尋可用模型 | Yes |
| 取得模型欄位定義 | Yes |
| 搜尋記錄 | Yes |
| 計數記錄 | Yes |
| 讀取指定 ID 記錄 | Yes |
| 建立記錄 | No |
| 更新記錄 | No |
| 刪除記錄(需二次確認) | No |
| 執行任意模型方法(萬用入口, | No |
| 上傳附件到 Odoo( | No |
| 取得 | No |
Docker 建置
部分 client 的 Docker 設定(Claude Code / Gemini 的 Docker 版本)需要先建置本機映像檔:
docker build -t odoo-mcp-server .MCP Client 設定
本專案支援以下 MCP Client,各自的完整設定步驟見對應章節:
Client | 加入方式 | 設定檔 |
|
| |
|
| |
手動編輯 |
| |
| OpenClaw config | |
|
|
Claude Code
設定檔位於 ~/.claude.json:
本機執行
claude mcp add odoo-mcp-server -- python odoo_mcp_server.py{
"mcpServers": {
"odoo-mcp-server": {
"command": "/bin/python",
"args": [
"odoo_mcp_server.py"
]
}
}
}Docker(host.docker.internal)
適用於 Odoo 執行在本機的情況:
claude mcp add odoo-mcp-server -- docker run -i --rm --add-host=host.docker.internal:host-gateway -e ODOO_URL=http://host.docker.internal:8069 -e ODOO_DATABASE=odoo19 -e ODOO_API_KEY=your_api_key_here odoo-mcp-server{
"mcpServers": {
"odoo-mcp-server": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"--add-host=host.docker.internal:host-gateway",
"-e",
"ODOO_URL=http://host.docker.internal:8069",
"-e",
"ODOO_DATABASE=odoo19",
"-e",
"ODOO_API_KEY=your_api_key_here",
"odoo-mcp-server"
]
}
}
}Docker(host network)
使用主機網路模式:
claude mcp add odoo-mcp-server -- docker run -i --rm --network host -e ODOO_URL=http://localhost:8069 -e ODOO_DATABASE=odoo19 -e ODOO_API_KEY=your_api_key_here odoo-mcp-server{
"mcpServers": {
"odoo-mcp-server": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"--network",
"host",
"-e",
"ODOO_URL=http://localhost:8069",
"-e",
"ODOO_DATABASE=odoo19",
"-e",
"ODOO_API_KEY=your_api_key_here",
"odoo-mcp-server"
]
}
}
}Docker(遠端 Odoo)
claude mcp add odoo-mcp-server -- docker run -i --rm -e ODOO_URL=https://example.com/ -e ODOO_DATABASE=odoo19 -e ODOO_API_KEY=your_api_key_here odoo-mcp-server{
"mcpServers": {
"odoo-mcp-server": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"-e",
"ODOO_URL=https://example.com/",
"-e",
"ODOO_DATABASE=odoo19",
"-e",
"ODOO_API_KEY=your_api_key_here",
"odoo-mcp-server"
]
}
}
}Gemini CLI
gemini mcp add --scope user odoo-mcp docker -- run -i --rm --add-host=host.docker.internal:host-gateway -e ODOO_URL=http://host.docker.internal:8069 -e ODOO_DATABASE=odoo19 -e ODOO_API_KEY=your_api_key_here odoo-mcp-server{
"mcpServers": {
"odoo-mcp": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"--add-host=host.docker.internal:host-gateway",
"-e",
"ODOO_URL=http://host.docker.internal:8069",
"-e",
"ODOO_DATABASE=odoo19",
"-e",
"ODOO_API_KEY=your_api_key_here",
"odoo-mcp-server"
]
}
}
}Antigravity CLI
自 2026/6/18 起個人版 Gemini CLI 停止服務,改用 Antigravity CLI。目前 沒有
mcp add子指令,需手動編輯設定檔。
設定檔路徑為 ~/.gemini/config/mcp_config.json(Antigravity CLI / IDE / SDK 共用,等同 Gemini CLI 的 --scope user)。
JSON 格式與上方 Gemini CLI 設定相同。
設定後進入 Antigravity CLI 以 /mcp 指令重新載入,並確認連線狀態。
OpenClaw
OpenClaw 透過 CLI 管理 MCP server,設定會寫入 mcp.servers.<name>。
/mcp指令為 owner-only 且預設關閉,需以commands.mcp: true開啟才能在 chat session 中使用。
步驟 1:註冊 MCP server
# 請將 your-server-ip 換成你的 MCP server 位址
openclaw mcp set odoo-mcp '{"type":"http","url":"http://your-server-ip:8000/mcp"}'OpenClaw 會自動正規化設定,把 type:"http" 轉成 transport:"streamable-http" 後存入:
{
"mcp": {
"servers": {
"odoo-mcp": {
"url": "http://your-server-ip:8000/mcp",
"transport": "streamable-http"
}
}
}
}步驟 2:開啟 /mcp 指令
openclaw config set commands.mcp true步驟 3:重啟 Gateway 套用設定
openclaw gateway restart若想等進行中的工作排空再重啟,可改用
openclaw gateway restart --safe。
驗證
# server 是否註冊成功
openclaw mcp list
openclaw mcp show odoo-mcp
# /mcp 開關狀態(應回傳 true)
openclaw config get commands.mcp完成後請開一個新的 chat session(或硬重整 dashboard),再輸入 /mcp 確認 odoo-mcp 連線狀態。
Codex CLI
Codex 的
codex mcp add只支援 stdio(command/args),並不支援 url(streamable HTTP)形式的遠端 server。因此要連雲端 HTTP 模式的 MCP server,需先用佔位指令建立設定,再手動編輯~/.codex/config.toml。
codex mcp add odoo-mcp -- echo placeholdercodex mcp add 產生的佔位設定:
[mcp_servers.odoo-mcp]
command = "echo"
args = ["placeholder"]手動改為 url(streamable HTTP):
[mcp_servers.odoo-mcp]
url = "https://your-cloud-server.com:8000/mcp"若 server 端設定了
MCP_AUTH_TOKEN,需加上bearer_token_env_var = "ODOO_MCP_TOKEN", 並在執行 Codex 的環境中export ODOO_MCP_TOKEN=<與 server MCP_AUTH_TOKEN 相同的值>; 或改用自訂http_headers直接填Authorizationheader。 多 user 模式(MCP_MULTIUSER=true)下同理,ODOO_MCP_TOKEN改 export 自己的 Odoo API key。
安全機制
部署定位與 HTTP 認證(MCP_AUTH_TOKEN)
設定 MCP_AUTH_TOKEN 環境變數即可啟用 Bearer Token 認證(opt-in):
# 產生隨機 token
openssl rand -hex 32啟用後
/mcp端點要求Authorization: Bearer <token>,未帶或錯誤一律回 401未設定時行為與過去版本相同(無認證),但 HTTP/SSE 模式啟動時會在 stderr 印出警告
多 user 模式(MCP_MULTIUSER)
預設情況下,所有操作都透過 ODOO_API_KEY 這一個 Odoo 帳號執行——多人共用時,
Odoo 端的權限、chatter、審計紀錄全部歸到同一個 user。設定 MCP_MULTIUSER=true
後改為 pass-through 認證:
每個 user 在 Odoo「偏好設定 → 帳戶安全」產生自己的 API key,直接當 HTTP/SSE 連線的 Bearer token(json2 protocol 的 API key 即完整憑證, Odoo 會從 key 解析出擁有者)
Server 收到 token 後拿去問 Odoo 驗證(結果快取 5 分鐘、驗證失敗快取 30 秒 以防暴力破解),驗證成功即建立綁該 user API key 的專屬連線
權限(ACL / record rules)、操作歸屬、審計全部由 Odoo 原生機制處理; 撤銷存取 = 在 Odoo 刪除該 API key(最遲 5 分鐘後生效)
Server 端不保存任何使用者資料,新增/移除 user 都在 Odoo 操作即可
MCP_AUTH_TOKEN可並存,作為 admin fallback(走ODOO_API_KEY的共享連線); stdio 模式不受影響純多 user 部署(不設
MCP_AUTH_TOKEN、不跑 stdio)可連ODOO_API_KEY都不設 ——server 端零長效憑證,設定檔外洩也沒東西可偷。誤走到 fallback 路徑時會收到 明確的設定錯誤訊息(不會拿 placeholder 去打 Odoo)Bearer token 就是 Odoo API key 本體,務必搭配 TLS
Client 端設定與 MCP_AUTH_TOKEN 完全相同(完整範例見雲端部署(HTTP 模式)),
只是 Bearer 換成各自的 Odoo API key:
"headers": {
"Authorization": "Bearer <你的 Odoo API key>"
}附件檔案讀取範圍(UPLOAD_DIR)
add_attachment 的 file_path 模式是本 server 唯一會讀取 MCP 主機本地檔案的入口。
若不設限,被 prompt injection 的 LLM 可用 file_path="/app/.env" 把 server 機密
(含 ODOO_API_KEY 本身)讀出、上傳成 Odoo 附件外洩——這是 confused deputy,
MCP_AUTH_TOKEN 擋不住(LLM 本來就是合法持 token 的 client)。
因此 file_path 被限制在 UPLOAD_DIR(預設 /shared/uploads)底下:
路徑經
Path.resolve()正規化後,必須落在UPLOAD_DIR內,否則回ToolErrorresolve()會一併解掉 symlink,所以「白名單目錄裡放一個指向外部的 symlink」也擋得掉預設值對齊 compose 的
/shared/uploads圖片傳遞通道,Docker 部署無需額外設定純本機 stdio 若要放行任意路徑,設
UPLOAD_DIR=/(等於解除限制,自負風險)檔案不在磁碟上(如 Discord 上傳的圖片)時,改用
base64_data模式,不受此限制
跨機器上傳圖片(prepare_upload → /upload)
當 client 與 server 不在同一台機器時,shared-uploads volume 用不到,file_path
沒有共用檔案系統可讀;若改走 base64_data,整包 base64 會流經 LLM 的 token stream,又慢又貴。
/upload 提供一條 out-of-band 的檔案通道:client 用普通 HTTP POST 把位元組直接推到 server
(不經 LLM),server 存進 UPLOAD_DIR 後回傳 file_path,client 再用這個路徑呼叫
add_attachment——只有短路徑字串會進 token stream。
整個工作流透過 MCP 協定自我描述,client 端零安裝、零設定:agent(如 Claude Code)呼叫
prepare_upload 工具就拿到端點用法與短效 upload_token,接著自己上傳:
# upload_token 由 prepare_upload 簽發(server 未設 MCP_AUTH_TOKEN 時免帶 header)
curl -fsS -F "file=@/local/invoice.png" \
-H "Authorization: Bearer <upload_token>" \
https://your-server:8000/upload
# → {"file_path": "/shared/uploads/<uuid>.png", "file_name": "invoice.png"}接著呼叫 add_attachment(file_path="/shared/uploads/<uuid>.png", file_name="invoice.png", ...)。
不經 MCP 的手動整合(腳本、CI 等)也可以直接拿 MCP_AUTH_TOKEN 本體打同一個端點。
安全機制:
/upload是會寫檔的端點,設了MCP_AUTH_TOKEN就要求 Bearer token(custom route 不受 MCP 認證保護,故自行驗證)prepare_upload簽發的是 HMAC 衍生短效 token(MCP_AUTH_TOKEN為根秘密簽出、預設 10 分鐘、只對/upload有效、無狀態驗證)——master token 不進 LLM context,就算對話 transcript 外流,外洩的也只是效期內的上傳權限磁碟檔名由 server 端
uuid產生,client 給的檔名絕不進入路徑,無法逃出UPLOAD_DIR單檔大小上限
UPLOAD_MAX_BYTES(預設 25 MiB),超過回 413不自動清理
UPLOAD_DIR,請搭配定期清理或使用 ephemeral volume
模型黑名單(MCP_ALLOW_SENSITIVE_MODELS)
預設封鎖以下兩個機密模型的所有讀寫,防範 prompt injection——LLM 讀到藏在資料裡的惡意指令,拿著你的 API key 做出「權限上合法、但你沒要求」的操作:
ir.config_parameter:存第三方 API key、webhook token 等機密,一句 search 就全外洩res.users.apikeys:可產生新的長效 API key——事後 rotate 原 key 也擋不住,等於留後門
正常的 agent 工作流程用不到這兩個模型,封鎖不影響日常使用;所有工具與 Resources(含 execute_method)都會檢查。
黑名單只是多一層保險,不是權限控管——權限仍由 Odoo ACL 決定,請給 MCP 低權限使用者的 API key (
res.users、ir.rule等安全模型因此不在黑名單內)。 真的需要透過 MCP 管理這兩個模型時,設MCP_ALLOW_SENSITIVE_MODELS=true停用。
唯讀模式
設定 READONLY_MODE=true 啟用唯讀模式,適用於生產環境查詢:
寫入工具(
create_record、update_record、delete_record、execute_method、add_attachment)在註冊時即被停用——LLM 看不到這些工具,直接呼叫也會被拒絕停用發生在模組層級,任何啟動方式(
python odoo_mcp_server.py、fastmcp run、fastmcp dev)都同樣生效
刪除二次確認與 execute_method 的安全邊界
delete_record 內建 confirm 機制:LLM 必須先以 confirm=False 呼叫取得確認提示,經使用者同意後才能以 confirm=True 執行刪除。
注意:confirm 參數由 LLM 自行填入,屬於「引導 LLM」層級的防護, 並非強制性的安全邊界(LLM 理論上可直接傳
confirm=True)。
execute_method 是萬用入口(escape hatch),用來呼叫沒有專用工具的模型方法,請理解其風險:
ORM 原語
unlink已被攔下,會導向delete_record的二次確認流程,無法藉此繞過確認模型黑名單同樣適用:credential 模型(
ir.config_parameter、res.users.apikeys)全擋但
action_confirm、action_post、button_validate這類會改資料的業務方法不設防—— Odoo 有上千個模型方法,server 無法枚舉哪些會寫入資料庫真正的安全邊界應該是給 MCP 使用低權限的 Odoo 使用者 API key(最小權限原則)
健康檢查
HTTP/SSE transport 模式下提供 /health 端點:
curl http://localhost:8000/health
# {"status": "healthy", "service": "odoo-mcp-server", "version": "1.2.0"}適用於 Docker healthcheck、Kubernetes probe、load balancer 探活。stdio 模式下不影響。
此端點不受 MCP_AUTH_TOKEN 保護(不需帶 token)。
License
Apache 2.0
Available Tools
11 toolsadd_attachmentAdd AttachmentA
Upload a file attachment to Odoo via ir.attachment.
Supports two input modes:
file_path: Read a local file from the MCP server's filesystem. The path must resolve to a location inside UPLOAD_DIR (default /shared/uploads); paths outside it are refused. If the file lives on the CLIENT machine and this server is remote, call prepare_upload first and POST the file to /upload to obtain a server-side file_path.
base64_data + file_name: Pass file content directly as base64 string. Use this when the file is not on disk (e.g., image uploaded via Discord).
| Name | Required | Description | Default |
|---|---|---|---|
| res_id | No | Record ID to attach to. Required if res_model is provided. | |
| file_name | No | Filename for the attachment (e.g., 'invoice.png'). Required with base64_data; optional with file_path (defaults to the file's own name — handy to restore a real name when file_path points at a server-generated /upload name). | |
| file_path | No | Path to a local file the MCP server can read. Must resolve to a location inside UPLOAD_DIR (default /shared/uploads); paths outside it are refused. Mutually exclusive with base64_data. | |
| res_model | No | Model to attach to (e.g., 'account.move'). None for standalone attachment. | |
| base64_data | No | File content as a base64-encoded string. Mutually exclusive with file_path. | |
| description | No | Optional description for the attachment. |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full burden. It discloses a genuine behavioral constraint: the path must resolve inside UPLOAD_DIR (default /shared/uploads) and paths outside are refused, plus the mutual exclusion of file_path and base64_data. However, it omits Odoo-side permission/auth requirements, size limits, and whether this creates a new ir.attachment record or overwrites existing ones, which matters for a mutation tool with zero annotation coverage.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loads the purpose in one line, then uses a tight two-bullet structure for the modes; no filler sentences. There is minor redundancy with the schema's UPLOAD_DIR constraint, but repeating a security-critical constraint in the description is defensible.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Both input modes, the cross-tool routing to prepare_upload, and the directory jail are covered, and an output schema exists so return values need no explanation. The remaining gap is behavioral, not structural: auth requirements and write-side effects go unmentioned for a mutation tool with no annotations.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the schema already documents every parameter, including the mutual exclusivity of file_path and base64_data and the res_id/res_model dependency. The description restates the mode semantics but adds no syntax or value format beyond the schema, so the baseline of 3 is correct.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Upload a file attachment to Odoo via ir.attachment') and immediately enumerates the two supported input modes, so the agent knows exactly what the tool does. The distinction from sibling create_record is implicit in the attachment-only scope, and the prepare_upload reference further locates it in the toolset.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives explicit mode-selection rules: use file_path when the file lives on the MCP server, base64_data when the file is not on disk (with a concrete example, 'image uploaded via Discord'). It names the alternative tool (prepare_upload) and the exact condition that selects it (remote server, client-side file), leaving nothing to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
count_recordsCount RecordsCRead-onlyIdempotent
Count records in an Odoo model matching the domain.
| Name | Required | Description | Default |
|---|---|---|---|
| model | Yes | Model name (e.g., 'res.partner') | |
| domain | No | Odoo search domain (list of conditions). Examples: - Simple: [["active", "=", True]] - Multiple (AND): [["is_company", "=", True], ["country_id", "=", 1]] - OR condition: ["|", ["type", "=", "contact"], ["type", "=", "invoice"]] - any (Odoo 19+): [["order_line", "any", [["product_uom_qty", ">", 5]]]] - Operators: =, !=, >, >=, <, <=, like, ilike, in, not in, child_of, any |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint and idempotentHint, so safety is covered. The description adds essentially nothing beyond that: it does not say what happens when domain is omitted (the null default), whether counting is bounded or expensive on large models, or how the count is returned.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single clause with zero filler, front-loading the verb and resource. It is arguably terse to the point of under-specification, which keeps it from a 5.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With an output schema present, return values need not be explained, and annotations cover the safety profile. What is missing is the default behavior when domain is null (count everything) and any cost/performance caveat, which matters for a count over arbitrary models.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, including extensive domain examples and the operator list, so the schema carries the full parameter burden. The description only restates that the domain filters, adding no syntax or format detail beyond the schema. Baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Count records in an Odoo model') plus the filtering mechanism ('matching the domain'), so the operation is unambiguous. It does not explicitly contrast itself with search_records or read_records, which is where it loses a point.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
There is no guidance on when to use this instead of the many sibling read tools (search_records, read_records), nor any prerequisite or exclusion. The agent must infer that this is the cheap alternative when only a number is needed.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
create_recordCreate RecordC
Create new record(s) in an Odoo model.
| Name | Required | Description | Default |
|---|---|---|---|
| model | Yes | Model name (e.g., 'res.partner') | |
| values | Yes | Dictionary of field values, or list of dicts for batch creation |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description bears the full behavioral burden. It never states required permissions, whether created records can be rolled back, how validation errors from Odoo constraints surface, or what a partial batch failure does — significant gaps for a mutation tool.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single short sentence with no padding, and the verb/resource are front-loaded. It is efficient, though the brevity reflects under-specification rather than disciplined economy.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return values need not be explained. However, with zero annotations and a write operation, the description should disclose at least side effects, permission needs, or batch error behavior; it discloses none of these.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, with 'model' and 'values' both documented in the schema (including the batch list option), so the baseline is 3. The description adds nothing beyond restating 'record(s)' and 'model', which the schema already covers more precisely.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb ('Create') and resource ('record(s) in an Odoo model'), making the operation immediately identifiable. It does not explicitly differentiate itself from siblings like update_record or delete_record, but the name/verb pairing is unambiguous enough for selection.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description offers no when-to-use guidance: no mention of choosing create_record over update_record, no context on when to batch-create versus single-create, and no prerequisites. The '(s)' hints batch capability but does not say when to use it.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
delete_recordDelete RecordADestructiveIdempotent
Delete records from an Odoo model. IRREVERSIBLE operation.
IMPORTANT: You MUST first call with confirm=False to show the user what will be deleted. Only set confirm=True AFTER the user explicitly approves the deletion. NEVER set confirm=True on the first call.
| Name | Required | Description | Default |
|---|---|---|---|
| ids | Yes | List of record IDs to delete | |
| model | Yes | Model name (e.g., 'res.partner') | |
| confirm | No | Safety flag. Always call with False first, then True only after user approval. |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare destructiveHint=true and idempotentHint=true, so the safety profile is partly covered; the description adds the irreversibility emphasis and, more importantly, the two-phase confirmation workflow that the annotations do not encode. It stops short of noting Odoo access-right requirements or cascade/error behavior, so it is not fully exhaustive.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the purpose and risk in one line, then the operational rule in the following lines. Every sentence carries a distinct constraint (what it does, that it cannot be undone, how to sequence the calls) with no filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists so return values need not be described, and annotations carry the destructive/idempotent profile. Combined with the fully documented 3-parameter schema, the description supplies exactly the missing piece an agent needs: the confirm-before-delete workflow.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the schema already documents ids, model and the confirm safety flag, giving a baseline of 3. The description adds meaning by attaching hard ordering semantics to confirm (False first, True only after approval), which constrains how the parameter must be used in a sequence rather than just what it stores.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Delete records from an Odoo model') and immediately flags the operation as IRREVERSIBLE, which cleanly separates it from create_record and update_record in the sibling set. An agent can identify the tool without opening any schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives an explicit, mandatory protocol: call with confirm=False first, set confirm=True only after explicit user approval, never on the first call. This is unambiguous when-to-use guidance with exclusions ('NEVER') rather than soft suggestion.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
execute_methodExecute MethodADestructive
Execute any method on an Odoo model (general-purpose escape hatch).
WARNING: Business methods (e.g. action_confirm, action_post, button_validate, toggle_active) can modify or irreversibly change data in Odoo and are NOT guarded by this server. Before calling any method that changes state, make sure the user explicitly asked for it.
| Name | Required | Description | Default |
|---|---|---|---|
| args | No | Positional arguments for the method | |
| model | Yes | Model name (e.g., 'res.partner') | |
| kwargs | No | Keyword arguments for the method | |
| method | Yes | Method name to execute |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Goes well beyond the destructiveHint=true annotation by naming concrete dangerous methods (action_confirm, action_post, button_validate, toggle_active), disclosing that state changes can be irreversible, and warning explicitly that the server does NOT guard these calls. That is exactly the context an agent needs beyond structured fields.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Purpose is front-loaded in the first sentence, followed immediately by the safety warning. No filler; every sentence carries weight for a dangerous tool.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return values need no explanation, and destructiveHint covers the safety profile. Combined with the explicit warning about unguarded business methods, an agent has everything needed to decide whether and how to call this.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so model, method, args, and kwargs are already documented in the schema. The description adds no format, ordering, or shape guidance for args/kwargs, so the baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (execute) and resource (method on an Odoo model) and self-classifies as a 'general-purpose escape hatch,' which cleanly distinguishes it from the CRUD-specific siblings like create_record, update_record, and delete_record.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Provides a clear precondition: 'Before calling any method that changes state, make sure the user explicitly asked for it.' This gives real when-to-use guidance, but it never routes the agent to the dedicated siblings (e.g. use update_record for standard writes) or states when this escape hatch is appropriate for reads.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_fieldsGet FieldsCRead-onlyIdempotent
Get field information for an Odoo model using ORM fields_get().
| Name | Required | Description | Default |
|---|---|---|---|
| model | Yes | Model name (e.g., 'res.partner') | |
| fields | No | Specific field names to retrieve (None = all fields) | |
| attributes | No | Field attributes to return (None = default attributes including type, string, help, required, readonly, store, selection, comodel_name, inverse_name, domain) | |
| field_filter | No | Optional filter for field name (e.g., 'name' to find name-related fields) |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and idempotentHint=true, so the safety profile is covered. The description adds no behavioral context beyond the ORM method name – nothing about metadata vs data, cost, or what happens with an unknown model. With annotations doing the heavy lifting it is not misleading, but it adds little.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single front-loaded sentence with no filler. The trailing ORM implementation reference is arguably the least useful part but is not wasteful enough to penalize heavily.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return values need not be explained. For a simple read-only metadata tool the description is minimally adequate, but it omits any guidance on filtering versus returning all fields – a distinction the description could clarify beyond the schema defaults.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100% with 4 well-documented params (model, fields, attributes, field_filter), so the schema already carries full parameter meaning. The description contributes nothing additional about parameters, making the baseline 3 correct.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (get) and resource (field information for an Odoo model), and names the underlying ORM call (fields_get()). An agent can tell it retrieves schema/metadata rather than record data. It does not explicitly contrast with list_models, so it stops short of 5.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
There is no guidance on when to use this tool versus list_models, read_records, or execute_method. No prerequisites, no when-not conditions, no mention of alternatives. Usage is left entirely to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_modelsList ModelsCRead-onlyIdempotent
List all available Odoo models.
| Name | Required | Description | Default |
|---|---|---|---|
| name_filter | No | Optional filter for model name (e.g., 'sale' to find sale-related models) |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and idempotentHint=true, covering the safety profile. The description adds no behavioral context beyond restating the basic operation—it does not mention scope (e.g., whether custom models are included), pagination, or return volume, so it contributes little beyond the structured fields.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single, front-loaded sentence with no wasted words. While extremely brief, it is appropriately sized for a simple listing tool, though it could be slightly more informative without becoming verbose.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the simple complexity, rich annotations, and an output schema, the description's minimal statement is barely adequate. It covers the core purpose but leaves usage relative to siblings and filtering behavior unaddressed, so it is just the minimum viable.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the single optional parameter (name_filter) is fully documented in the schema with an example. The description does not mention filtering at all, which is acceptable given the schema's completeness, resulting in the baseline score of 3.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb ('List') and resource ('Odoo models'), making the tool's purpose immediately clear. However, it does not differentiate from sibling tools like get_fields or execute_method, which also interact with Odoo metadata.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides no guidance on when to use this tool versus alternatives such as get_fields or search_records. It omits any context, prerequisites, or exclusions, leaving usage entirely to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
prepare_uploadPrepare UploadA
Prepare an out-of-band upload of a CLIENT-side file to this server.
Use this when the file to attach lives on the client machine and this MCP server is remote: pushing the bytes over plain HTTP keeps them out of the LLM token stream (much faster and cheaper than base64_data for large files).
Workflow:
Call this tool to get the endpoint info and a short-lived upload_token.
POST the file (multipart field 'file') to /upload, where is the same scheme://host:port this MCP connection uses: curl -fsS -F "file=@/local/invoice.png"
-H "Authorization: Bearer <upload_token>" /upload -> {"file_path": "/shared/uploads/.png", "file_name": "invoice.png"}Call add_attachment(file_path=..., file_name=..., res_model=..., res_id=...).
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations, so the description carries the full burden and does: it discloses that a short-lived upload_token is issued, that the upload endpoint is <origin>/upload via multipart field 'file', that auth is a Bearer header, and even the response shape ({file_path, file_name}). This is exactly the behavioral context needed for a credential-issuing tool.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded purpose sentence followed by a numbered workflow. The curl example and one-line JSON response are dense, useful payload rather than padding; every sentence earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a zero-param, token-issuing tool with a sibling chain, the description supplies endpoint construction, auth, request format, expected response, and the next tool to call. Nothing an agent needs to execute the workflow is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Zero parameters, so the baseline is 4. The description adds the semantics of what the tool yields (endpoint info + token) and how to consume it, which schema alone cannot express.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Specific verb+resource ('Prepare an out-of-band upload of a CLIENT-side file to this server') with the scope narrowed to client-side files on a remote server. It clearly distinguishes itself from add_attachment, which it routes to as the follow-up step.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicit when-to-use ('when the file to attach lives on the client machine and this MCP server is remote') and names the avoided alternative (base64_data in the token stream) with the reason. No inference required.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
read_recordsRead RecordsBRead-onlyIdempotent
Read specific records by their IDs.
| Name | Required | Description | Default |
|---|---|---|---|
| ids | Yes | List of record IDs to read | |
| model | Yes | Model name (e.g., 'res.partner') | |
| fields | No | Fields to return (None = auto-exclude dangerous fields like binary/image/html) |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The readOnlyHint and idempotentHint annotations already establish the safety profile, and the description is consistent with them. However it adds no behavioral context beyond the annotations — nothing about behavior for non-existent/duplicate IDs, missing models, or the field-exclusion default that the schema mentions.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single front-loaded sentence with zero filler or repetition. It is arguably too terse for a tool with three parameters, but it is not padded, so structure and brevity are good.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return-value details need not be explained, and the schema fully documents the inputs. Still, this read tool sits among search_records and list_models without any routing guidance, leaving the agent to work out which read variant to pick.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so all three parameters (ids, model, fields) are already self-documented in the schema, including the dangerous-field auto-exclusion behavior of fields. The description adds no parameter-level detail beyond restating that lookup is by ID, so the baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb plus resource plus the selection mechanism ("Read specific records by their IDs"), which implicitly separates it from the batched search_records sibling. It never names the alternative tool explicitly, so differentiation still requires inference from the sibling list.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The phrase "by their IDs" implies the precondition for using this tool (you already have IDs) versus searching, but there is no explicit when-to-use/when-not statement and no mention of search_records as the alternative path. Usage is inferable rather than stated.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
search_recordsSearch RecordsCRead-onlyIdempotent
Search for records in an Odoo model.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Maximum number of records | |
| model | Yes | Model name (e.g., 'res.partner') | |
| order | No | Sort order (e.g., 'name asc', 'create_date desc') | |
| domain | No | Odoo search domain (list of conditions). Examples: - Simple: [["name", "=", "John"]] - Multiple (AND): [["is_company", "=", True], ["active", "=", True]] - OR condition: ["|", ["name", "ilike", "test"], ["email", "ilike", "test"]] - any (Odoo 19+): [["order_line", "any", [["product_uom_qty", ">", 5]]]] - Operators: =, !=, >, >=, <, <=, like, ilike, in, not in, child_of, any | |
| fields | No | Fields to return (None = auto-exclude dangerous fields like binary/image/html) | |
| offset | No | Number of records to skip |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and idempotentHint=true, so safety is covered, but the description adds nothing on top: no note on pagination semantics (limit/offset interaction), no mention that unset fields auto-exclude binary/html, no return-shape context. For a search tool with real behavioral quirks, this is a bare minimum.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single short sentence with no padding and nothing buried; it is front-loaded. It is efficient rather than wasteful, though the brevity reflects under-specification that the other dimensions already penalize.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists so return values need not be explained, and all six parameters are documented in the schema. The remaining gap is contextual: no hint of Odoo domain usage or how this relates to the eight sibling record tools, which a description for a search entry point should at least gesture at.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, and the domain/order/fields parameters are documented in the schema with examples, so the schema does the heavy lifting. The description adds no parameter meaning beyond that, which lands at the baseline 3.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a verb (Search) and a resource (records in an Odoo model), so the basic purpose is legible. However, it is so terse that it fails to separate this tool from close siblings like read_records, count_records, or list_models, leaving the agent to guess at scope.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
There is no when-to-use guidance at all: nothing says to pick this over read_records or count_records, no mention of the domain-based filtering that is the tool's distinguishing feature, and no preconditions. The agent gets no routing help.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
update_recordUpdate RecordCIdempotent
Update existing records in an Odoo model.
| Name | Required | Description | Default |
|---|---|---|---|
| ids | Yes | List of record IDs to update | |
| model | Yes | Model name (e.g., 'res.partner') | |
| values | Yes | Dictionary of field values to update |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only declare idempotentHint=true, so the description carries most of the behavioral burden. It says 'existing records', hinting that nonexistent IDs are not created, but says nothing about batch semantics, partial failure behavior, or required access rights for a mutation tool.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single short sentence with the verb and resource front-loaded and no wasted words. It is efficient, though its brevity is partly the same under-specification captured in other dimensions.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a generic Odoo write tool with nested values and no destructive/read-only annotation, the description leaves out error behavior, permission needs, and return expectations. The presence of an output schema removes the need to describe return values, but the behavioral gaps remain.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so model, ids, and values are already documented in the schema. The description adds no format or constraint detail beyond what the schema provides, making the baseline 3 appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb and resource (update existing records) and names the domain (Odoo model), so it is clearly distinguishable from create_record, delete_record, and read_records by verb alone. It does not explicitly contrast itself with those siblings, but the verb is unambiguous.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
There is no guidance on when to use this tool versus alternatives such as create_record or execute_method, and no prerequisites (permissions, valid model names) are mentioned. The agent must infer usage entirely from the verb.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
2 tool updates
v1.2.0- Added
add_attachment - Added
prepare_upload
9 tool updates
- Changed
count_records2 fields changed- added
Input schema / properties / domain / descriptionAdded value: +"Odoo search domain (list of conditions). Examples:\n- Simple: [[\"active\", \"=\", True]]\n- Multiple (AND): [[\"is_company\", \"=\", True], [\"country_id\", \"=\", 1]]\n- OR condition: [\"|\", [\"type\", \"=\", \"contact\"], [\"type\", \"=\", \"invoice\"]]\n- any (Odoo 19+): [[\"order_line\", \"any\", [[\"product_uom_qty\", \">\", 5]]]]\n- Operators: =, !=, >, >=, <, <=, like, ilike, in, not in, child_of, any" - added
Input schema / properties / model / descriptionAdded value: +"Model name (e.g., 'res.partner')"
- Changed
create_record2 fields changed- added
Input schema / properties / model / descriptionAdded value: +"Model name (e.g., 'res.partner')" - added
Input schema / properties / values / descriptionAdded value: +"Dictionary of field values, or list of dicts for batch creation"
- Changed
delete_record3 fields changed- added
Input schema / properties / confirm / descriptionAdded value: +"Safety flag. Always call with False first, then True only after user approval." - added
Input schema / properties / ids / descriptionAdded value: +"List of record IDs to delete" - added
Input schema / properties / model / descriptionAdded value: +"Model name (e.g., 'res.partner')"
- Changed
execute_method4 fields changed- added
Input schema / properties / args / descriptionAdded value: +"Positional arguments for the method" - added
Input schema / properties / kwargs / descriptionAdded value: +"Keyword arguments for the method" - added
Input schema / properties / method / descriptionAdded value: +"Method name to execute" - added
Input schema / properties / model / descriptionAdded value: +"Model name (e.g., 'res.partner')"
- Changed
get_fields4 fields changed- added
Input schema / properties / attributes / descriptionAdded value: +"Field attributes to return (None = default attributes including\n type, string, help, required, readonly, store, selection,\n comodel_name, inverse_name, domain)" - added
Input schema / properties / field_filter / descriptionAdded value: +"Optional filter for field name (e.g., 'name' to find name-related fields)" - added
Input schema / properties / fields / descriptionAdded value: +"Specific field names to retrieve (None = all fields)" - added
Input schema / properties / model / descriptionAdded value: +"Model name (e.g., 'res.partner')"
- Changed
list_models1 field changed- added
Input schema / properties / name_filter / descriptionAdded value: +"Optional filter for model name (e.g., 'sale' to find sale-related models)"
- Changed
read_records3 fields changed- added
Input schema / properties / fields / descriptionAdded value: +"Fields to return (None = auto-exclude dangerous fields like binary/image/html)" - added
Input schema / properties / ids / descriptionAdded value: +"List of record IDs to read" - added
Input schema / properties / model / descriptionAdded value: +"Model name (e.g., 'res.partner')"
- Changed
search_records6 fields changed- added
Input schema / properties / domain / descriptionAdded value: +"Odoo search domain (list of conditions). Examples:\n- Simple: [[\"name\", \"=\", \"John\"]]\n- Multiple (AND): [[\"is_company\", \"=\", True], [\"active\", \"=\", True]]\n- OR condition: [\"|\", [\"name\", \"ilike\", \"test\"], [\"email\", \"ilike\", \"test\"]]\n- any (Odoo 19+): [[\"order_line\", \"any\", [[\"product_uom_qty\", \">\", 5]]]]\n- Operators: =, !=, >, >=, <, <=, like, ilike, in, not in, child_of, any" - added
Input schema / properties / fields / descriptionAdded value: +"Fields to return (None = auto-exclude dangerous fields like binary/image/html)" - added
Input schema / properties / limit / descriptionAdded value: +"Maximum number of records" - added
Input schema / properties / model / descriptionAdded value: +"Model name (e.g., 'res.partner')" - added
Input schema / properties / offset / descriptionAdded value: +"Number of records to skip" - added
Input schema / properties / order / descriptionAdded value: +"Sort order (e.g., 'name asc', 'create_date desc')"
- Changed
update_record3 fields changed- added
Input schema / properties / ids / descriptionAdded value: +"List of record IDs to update" - added
Input schema / properties / model / descriptionAdded value: +"Model name (e.g., 'res.partner')" - added
Input schema / properties / values / descriptionAdded value: +"Dictionary of field values to update"
9 tool updates
v1.0.0- First observed
count_records - First observed
create_record - First observed
delete_record - First observed
execute_method - First observed
get_fields - First observed
list_models - First observed
read_records - First observed
search_records - First observed
update_record
TDQS
Scored across 11 tools
The CRUD set (search/read/create/update/delete/count) is clearly differentiated by action, and metadata tools (list_models, get_fields) are distinct. The main blur is execute_method, a general escape hatch that can perform any of the CRUD operations, plus the coupled prepare_upload/add_attachment pair which requires understanding a shared workflow.
Nearly all tools follow a verb_noun snake_case pattern (list_models, search_records, create_record, delete_record). Minor inconsistency between plural nouns (search_records, count_records, read_records) and singular (create_record, update_record, delete_record), but conventions are otherwise readable and predictable.
11 tools is well-scoped for an Odoo ORM gateway: a clean CRUD+search core, two metadata/introspection helpers, an escape hatch, and two attachment tools. No redundant or filler tools.
Covers full CRUD, search, count, model/field introspection, and file attachments, which is solid lifecycle coverage for an Odoo data server. Minor gaps like a combined search_read or bulk update exist, and execute_method serves as a catch-all fallback.
Maintenance
Related MCP Connectors
Odoo MCP Pack — ERP/CRM via Odoo's external JSON-RPC API.
MCP server for Product Management
MCP server for the Seline Analytics API
MCP server unifying ERPs, CRMs, APIs and knowledge base for Claude, ChatGPT and Gemini.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceAn MCP server that connects AI assistants to Odoo ERP instances via the built-in XML-RPC API without requiring any additional addons. It enables users to search, create, update, and manage Odoo records and models through natural language.34 npmMIT
- AlicenseNot gradedqualityDmaintenanceMCP server for interacting with Odoo/ODX resources via ODXProxy, enabling programmatic access for MCP-compatible clients.1MIT
- FlicenseNot gradedqualityDmaintenanceMCP server to connect Claude with Odoo 18, enabling CRUD operations on Odoo models via natural language.2-
- AlicenseNot gradedqualityDmaintenanceA professional MCP server for seamless Odoo ERP integration, supporting HTTP and STDIO transports.13 npmMIT