A security-focused, version-agnostic gateway for Odoo (v17-19) that allows AI agents to interact with models via CRUD operations, schema discovery, and specialized domain plugins for HR, Sales, Project, and Helpdesk modules.
odoo-mcp-gateway
Security-first, version-agnostic MCP gateway for Odoo 17/18/19. Works with stock and custom modules via YAML configuration. Zero Odoo-side code required.
Why This Exists
Existing Odoo MCP servers share common problems: hardcoded model lists that miss custom modules, security as an afterthought, mandatory custom Odoo addons, and single-version targets. This gateway solves all of them:
Two-layer security — MCP restrictions (YAML) + Odoo's built-in ACLs (ir.model.access + ir.rule)
YAML-driven configuration — model restrictions, RBAC, field-level access, rate limiting, audit logging
Custom module support — auto-discovers models via
ir.model, add YAML config and it worksVersion-agnostic — Odoo 17, 18, 19 with version-specific adapters
Zero Odoo-side code —
pip install+ YAML config = done. No custom addon requiredFull MCP primitives — 27 Tools + 5 Resources + 7 Prompts (most servers only implement Tools)
Plugin architecture — extend with pip-installable domain packs via entry_points
Architecture
MCP Client (Claude Desktop / Claude Code / HTTP)
| User calls login tool with Odoo credentials
v
MCP Server (FastMCP)
|
|-- security_gate() --> Rate limit + RBAC tool access + audit logging
|-- restrictions --> Model/method/field block lists (YAML + hardcoded)
|-- rbac --> Field-level filtering + write sanitization
|
|-- tools/ --> 27 MCP tools (auth + schema + CRUD + plugins)
|-- resources/ --> 5 MCP resources (odoo:// URIs)
|-- prompts/ --> 7 reusable prompt templates
|-- plugins/ --> Entry-point plugin system (HR, Sales, Project, Helpdesk)
|
| JSON-RPC / XML-RPC as authenticated user
v
Odoo 17/18/19 (security enforced per user via ir.model.access + ir.rule)Security Pipeline
Every tool and resource call passes through this pipeline:
Request --> Rate Limit --> Authentication Check --> RBAC Tool Access
--> Model Restriction --> Method Restriction --> Field Validation
--> Handler Execution --> RBAC Field Filtering --> Audit Log --> ResponseHardcoded safety guardrails that cannot be overridden by YAML:
18 always-blocked models (ir.config_parameter, ir.cron, ir.module.module, ir.rule, ir.mail_server, etc.)
18 always-blocked methods (sudo, with_user, with_env, _sql, _write, _create, etc.)
28 ORM methods blocked in execute_method (prevents bypassing field-level checks)
Quick Start
pip install odoo-mcp-gateway
# Copy and edit config files
cp config/restrictions.yaml.example config/restrictions.yaml
cp config/model_access.yaml.example config/model_access.yaml
cp config/rbac.yaml.example config/rbac.yaml
# Set environment variables
export ODOO_URL=http://localhost:8069
export ODOO_DB=mydb
# Run (stdio mode for Claude Desktop / Claude Code)
python -m odoo_mcp_gateway
# Or HTTP mode for web clients
MCP_TRANSPORT=streamable-http python -m odoo_mcp_gatewayClaude Desktop Configuration
Add to claude_desktop_config.json:
{
"mcpServers": {
"odoo": {
"command": "python",
"args": ["-m", "odoo_mcp_gateway"],
"env": {
"ODOO_URL": "http://localhost:8069",
"ODOO_DB": "mydb"
}
}
}
}Claude Code Configuration
# Add as MCP server
claude mcp add odoo -- python -m odoo_mcp_gatewayEnvironment Variables
Variable | Default | Description |
|
| Odoo server URL |
| (required) | Odoo database name |
|
| Transport mode ( |
|
| HTTP host (streamable-http mode) |
|
| HTTP port (streamable-http mode) |
|
| Logging level |
Security
Two-Layer Security Model
MCP gateway restrictions (YAML config + hardcoded guardrails) — blocks sensitive models, dangerous methods, privileged fields before any Odoo call is made
Odoo's built-in ACLs — enforces per-user access on actual records via
ir.model.accessandir.rule
Model Restriction Tiers
Tier | Effect | Example |
| Nobody can access, including admins |
|
| Only admin users |
|
| Read OK for all, write needs admin |
|
Hardcoded Safety Guardrails
These cannot be overridden by YAML configuration:
Blocked models (17): ir.config_parameter, res.users.apikeys, ir.cron, ir.module.module, ir.model.access, ir.rule, ir.mail_server, ir.ui.view, ir.actions.server, res.config.settings, change.password.wizard, change.password.user, base.module.update, base.module.upgrade, base.module.uninstall, fetchmail.server, bus.bus
Blocked methods (18): sudo, with_user, with_company, with_context, with_env, with_prefetch, _auto_init, _sql, _register_hook, _write, _create, _read, _setup_base, _setup_fields, _setup_complete, init, _table_query, _read_group_raw
Additional Security Features
Rate limiting — per-session token bucket with separate global and write budgets
RBAC — tool-level access control by user group, field-level response filtering
Input validation — model names, method names, field names, domain filters, ORDER BY clauses, write values (size/depth/type)
IDOR protection — plugin tools scope data access to the authenticated user
Audit logging — structured JSON logs for all allowed and denied operations
Error sanitization — strips internal URLs, SQL fragments, file paths, stack traces from error messages
XXE protection — XML-RPC responses parsed with
defusedxmlDomain validation — Odoo domain filters validated for operators, field names, value types, nesting depth, and list sizes
Authentication
Three stock Odoo auth methods — no custom addon needed:
Method | Protocol | Use Case |
| XML-RPC | Server-to-server, CI/CD pipelines |
| JSON-RPC | Interactive users, Claude Desktop |
| JSON-RPC | Reuse existing browser session (development) |
# Example: login via the MCP tool
> login(method="password", username="admin", credential="admin", database="mydb")MCP Tools (11)
Tool | Description |
| Authenticate with Odoo (api_key / password / session) |
| List accessible models with metadata and keyword filter |
| Get field definitions for a model with optional filter |
| Search records with domain filters, field selection, ordering |
| Get a single record by ID |
| Count matching records |
| Create a new record (validates field names and values) |
| Update existing record (validates field names and values) |
| Delete a single record by ID |
| Aggregated grouped reads with aggregate functions |
| Call allowed model methods (validates method name) |
MCP Resources (5)
URI | Description |
| List all accessible models |
| Model detail with field definitions |
| Single record data with RBAC field filtering |
| Field schema with type info and importance ranking |
| Model categories with counts |
MCP Prompts (7)
Prompt | Description |
| Comprehensive model structure analysis |
| Natural language data exploration guide |
| Guide through model-specific workflows |
| Side-by-side record comparison |
| Analytical report generation |
| Find and understand custom modules |
| Troubleshoot access and permission issues |
Built-in Domain Plugins
HR Plugin
Tool | Description |
| Record attendance check-in |
| Record attendance check-out |
| View attendance records (with month filter) |
| View leave requests (with state filter) |
| Submit a leave request |
| View employee profile |
Sales Plugin
Tool | Description |
| List quotations/orders (with state filter) |
| Full order details with line items |
| Confirm a draft/sent quotation |
| Aggregated sales statistics (with period filter) |
Project Plugin
Tool | Description |
| List assigned tasks (with state/project filter) |
| Project stats: task counts by stage, overdue |
| Move a task to a different stage |
Helpdesk Plugin
Tool | Description |
| List assigned tickets (with state/priority filter) |
| Create a new helpdesk ticket |
| Move a ticket to a different stage |
Custom Module Support
Add custom Odoo modules without writing Python code. Edit model_access.yaml:
custom_models:
full_crud:
- custom.delivery.route
- custom.warehouse.zone
read_only:
- custom.delivery.log
allowed_methods:
custom.delivery.route:
- action_dispatch
- action_complete
- action_cancelThen all CRUD tools (search_read, create_record, update_record, delete_record) and execute_method work on the custom models with full security enforcement.
Plugin System
Extend the gateway with pip-installable plugins:
from odoo_mcp_gateway.plugins.base import OdooPlugin
class ManufacturingPlugin(OdooPlugin):
@property
def name(self) -> str:
return "manufacturing"
@property
def required_odoo_modules(self) -> list[str]:
return ["mrp"]
@property
def required_models(self) -> list[str]:
return ["mrp.production", "mrp.bom"]
def register(self, server, context):
@server.tool()
async def get_production_orders(...):
...Register via pyproject.toml entry points:
[project.entry-points."odoo_mcp_gateway.plugins"]
manufacturing = "my_package:ManufacturingPlugin"Configuration Files
File | Purpose |
| Model/method/field block lists (3 tiers) |
| Per-model access policies, allowed methods, sensitive fields |
| Role-based tool access and field filtering by group |
| Server, connection, auth settings |
All files have .example templates with extensive inline documentation. Copy and customize:
cp config/restrictions.yaml.example config/restrictions.yaml
cp config/model_access.yaml.example config/model_access.yaml
cp config/rbac.yaml.example config/rbac.yamlExample: Restrict a Model
# restrictions.yaml
restrictions:
always_blocked:
- my.secret.model
admin_only:
- hr.salary.rule
admin_write_only:
- res.company
blocked_write_fields:
- password_crypt
- api_key
- totp_secretExample: RBAC by Group
# rbac.yaml
rbac:
tool_group_requirements:
delete_record:
- base.group_system
execute_method:
- base.group_erp_manager
sensitive_fields:
hr.employee:
salary:
required_group: hr.group_hr_managerDocker
cp .env.example .env # Edit with your Odoo settings
docker compose upServices:
MCP Gateway — port 8080 (streamable-http mode)
Odoo 18 — internal only (no host port exposed by default)
PostgreSQL — internal only
The gateway runs as a non-root user in a minimal Python image.
CLI Tools
# Test Odoo connectivity
odoo-mcp-tools test-connection --url http://localhost:8069
# Validate all YAML config files
odoo-mcp-tools validate-config --config-dir config
# List configured model access policies
odoo-mcp-tools list-models --config-dir configDevelopment
git clone https://github.com/parth-unjiya/odoo-mcp-gateway.git
cd odoo-mcp-gateway
pip install -e ".[dev]"
# Run tests
pytest tests/ -v
# Run with coverage
pytest tests/ --cov=odoo_mcp_gateway --cov-report=term-missing
# Lint
ruff check src/ tests/
# Type check (strict mode)
mypy src/Source Layout
src/odoo_mcp_gateway/
├── __main__.py # Entry point (stdio + HTTP)
├── server.py # FastMCP server setup, tool registration
├── config.py # Pydantic settings (env + .env)
├── client/
│ ├── base.py # OdooClientBase ABC, AuthResult
│ ├── jsonrpc.py # JSON-RPC client (session auth)
│ ├── xmlrpc.py # XML-RPC client (API key auth, defusedxml)
│ └── exceptions.py # OdooError hierarchy (7 types)
├── core/
│ ├── auth/manager.py # 3 auth strategies
│ ├── connection/manager.py # Circuit breaker + retry
│ ├── version/ # Odoo 17/18/19 detection + adapters
│ ├── security/
│ │ ├── restrictions.py # 3-tier model/method restrictions + hardcoded guardrails
│ │ ├── rbac.py # Tool access + field filtering
│ │ ├── middleware.py # Security pipeline + security_gate()
│ │ ├── rate_limit.py # Token bucket rate limiter
│ │ ├── audit.py # Structured audit logging
│ │ ├── sanitizer.py # Error message sanitization
│ │ └── config_loader.py # YAML config → Pydantic models
│ └── discovery/
│ ├── model_registry.py # ir.model auto-discovery
│ ├── field_inspector.py # fields_get with TTL cache
│ └── suggestions.py # Category search + related models
├── tools/
│ ├── auth.py # login tool
│ ├── schema.py # list_models, get_model_fields
│ └── crud.py # search_read, create/update/delete, execute_method
├── resources/handlers.py # 5 MCP resources (odoo:// URIs)
├── prompts/handlers.py # 7 MCP prompt templates
├── plugins/
│ ├── base.py, registry.py # Plugin ABC + entry_point discovery
│ └── core/ # Built-in plugins (HR, Sales, Project, Helpdesk)
├── cli/tools.py # CLI: test-connection, validate-config
└── utils/ # Domain builder, formatting, token budgetTesting
1,043 tests across all layers with 93% code coverage:
tests/unit/
├── client/ # JSON-RPC, XML-RPC, auth manager, XXE protection
├── security/ # Restrictions, RBAC, audit, rate limit, sanitizer, security_gate
├── discovery/ # Model registry, field inspector, suggestions
├── tools/ # All 11 MCP tools + input validation
├── plugins/ # Plugin system + 4 domain plugins + IDOR protection
└── cli/ # CLI utility tools# Run all tests
pytest tests/ -v
# Run specific area
pytest tests/unit/security/ -v
pytest tests/unit/tools/ -v
pytest tests/unit/plugins/ -v
# Coverage report
pytest tests/ --cov=odoo_mcp_gateway --cov-report=htmlError Handling
All Odoo errors are classified into 7 types:
Error | Cause |
| Cannot reach Odoo server |
| Invalid credentials |
| ir.model.access denied |
| Field validation failure |
| Business logic error |
| Record not found |
| Unsupported Odoo version |
All error messages are sanitized before reaching the MCP client — internal URLs, SQL fragments, file paths, and stack traces are automatically stripped.
Known Limitations
Single-user stdio mode: The gateway is designed for single-user
stdiotransport (Claude Desktop, Claude Code). Multi-userstreamable-httpmode works but sessions are not fully isolated between concurrent users.XML-RPC credential handling: When using API key authentication (XML-RPC), the credential is sent with every RPC call as required by the protocol. Use HTTPS in production.
Admin detection: Admin status is derived from Odoo group membership. Non-English Odoo instances may need configuration adjustments for group name resolution.
Contributing
Fork the repository
Create a feature branch (
git checkout -b feature/my-feature)Make your changes with tests
Ensure all checks pass:
pytest && ruff check src/ tests/ && mypy src/Submit a pull request
License
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.