# Odoo Mcp Gateway

> Security-first MCP server for Odoo 17/18/19 — YAML-driven security, zero Odoo-side code, 27 tools + 5 resources + 7 prompts

- **Type:** MCP server
- **Install:** `agentstack add mcp-parth-unjiya-odoo-mcp-gateway`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [parth-unjiya](https://agentstack.voostack.com/s/parth-unjiya)
- **Installs:** 0
- **Category:** [AI & ML](https://agentstack.voostack.com/c/ai-and-ml)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [parth-unjiya](https://github.com/parth-unjiya)
- **Source:** https://github.com/parth-unjiya/odoo-mcp-gateway

## Install

```sh
agentstack add mcp-parth-unjiya-odoo-mcp-gateway
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

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

[](https://www.python.org/)
[](LICENSE)
[](https://www.odoo.com/)
[](#testing)
[](#testing)

## 30-Second Quick Start

```bash
pip install odoo-mcp-gateway
```

Add to Claude Desktop config (`claude_desktop_config.json`):

```json
{
  "mcpServers": {
    "odoo": {
      "command": "python",
      "args": ["-m", "odoo_mcp_gateway"],
      "env": {
        "ODOO_URL": "http://localhost:8069",
        "ODOO_DB": "your_database"
      }
    }
  }
}
```

Restart Claude Desktop. In any conversation:

```
login with method "password", username "admin", credential "your_password"
```

You're connected. Ask Claude to query, create, or update Odoo records — every call is rate-limited, audit-logged, and runs through two layers of security checks before reaching Odoo.

**No Odoo addon required. No Python code to write.** Just YAML config for fine-grained access control (optional — secure defaults work out of the box).

## What's New in v0.2.1

- **Brute-force protection** — per-username (5/5min) + per-source (30/15min) lockout
- **`dry_run` mode** on `create_record`, `update_record`, `delete_record`, `execute_method` — validate without executing
- **2 new tools**: `get_defaults` (preview Odoo defaults), `get_onchange` (preview field side effects)
- **Temporal grouping** in `read_group`: `create_date:month`, `date:quarter`, etc.
- **Hardened blocklists**: 32 always-blocked models (was 17), 29 always-blocked methods (was 18), 10 always-blocked write fields, 8 always read-only models
- **Server-side admin verification** via `has_group('base.group_system')` (was trusted from auth response)
- **Credential wrapper** prevents password leakage via `repr()`/traceback
- **JSON-RPC retry** only fires on `OdooSessionExpiredError` (was retrying on every auth error)

See [CHANGELOG.md](CHANGELOG.md) for the full list of 21 security fixes.

## 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 works
- **Version-agnostic** — Odoo 17, 18, 19 with version-specific adapters
- **Zero Odoo-side code** — `pip install` + YAML config = done. No custom addon required
- **Full MCP primitives** — 31 Tools + 6 Resources + 12 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/             --> 31 MCP tools (auth + schema + CRUD + workflow + plugins)
    |-- resources/         --> 6 MCP resources (odoo:// URIs)
    |-- prompts/           --> 12 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 --> Response
```

Hardcoded safety guardrails that cannot be overridden by YAML:
- **32 always-blocked models** — system internals, auth/TOTP, payment tokens, attachments, mail.mail, base.automation, and more
- **8 always read-only models** — mail.message, mail.followers, mail.activity, discuss.channel, mail.notification, mail.compose.message, mail.alias, discuss.channel.member (reads OK, writes blocked for everyone)
- **10 always-blocked write fields** — password, password_crypt, groups_id, totp_secret, signup_token/type/expiration, api_key, share, active
- **29 always-blocked methods** — sudo, with_user/env/context, _sql, _write, _create, name_create, load, import_data, export_data, and more
- **28 ORM methods blocked in execute_method** (prevents bypassing field-level checks)
- **Per-username brute-force lockout** — 5 failures → 5 minute lockout (fixed duration, cannot be extended)
- **Per-source brute-force lockout** — 30 failures / 15 min, prevents username-rotation attacks
- **Credential wrapper class** — passwords stored with leak-safe `__repr__`/`__str__`, cleared on close
- **Server-side admin verification** — `has_group('base.group_system')` overrides auth-response `is_admin`

## Quick Start

```bash
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_gateway
```

### Claude Desktop Configuration

Add to `claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "odoo": {
      "command": "python",
      "args": ["-m", "odoo_mcp_gateway"],
      "env": {
        "ODOO_URL": "http://localhost:8069",
        "ODOO_DB": "mydb"
      }
    }
  }
}
```

### Claude Code Configuration

```bash
# Add as MCP server
claude mcp add odoo -- python -m odoo_mcp_gateway
```

### Environment Variables

| Variable | Default | Description |
|----------|---------|-------------|
| `ODOO_URL` | `http://localhost:8069` | Odoo server URL |
| `ODOO_DB` | *(required)* | Odoo database name |
| `MCP_TRANSPORT` | `stdio` | Transport mode (`stdio` or `streamable-http`) |
| `MCP_HOST` | `127.0.0.1` | HTTP host (streamable-http mode) |
| `MCP_PORT` | `8080` | HTTP port (streamable-http mode) |
| `MCP_LOG_LEVEL` | `INFO` | Logging level |
| `CONFIG_DIR` | `.` | Directory for YAML config files |
| `SESSION_TIMEOUT_SECONDS` | `1800` | Session inactivity timeout |
| `MAX_CONCURRENT_SESSIONS` | `100` | Maximum concurrent sessions |
| `RATE_LIMIT_GLOBAL` | `60` | Requests per minute (global) |
| `RATE_LIMIT_WRITE` | `20` | Write operations per minute |

## Security

### Two-Layer Security Model

1. **MCP gateway restrictions** (YAML config + hardcoded guardrails) — blocks sensitive models, dangerous methods, privileged fields *before* any Odoo call is made
2. **Odoo's built-in ACLs** — enforces per-user access on actual records via `ir.model.access` and `ir.rule`

### Model Restriction Tiers

| Tier | Effect | Example |
|------|--------|---------|
| `always_blocked` | Nobody can access, including admins | `ir.config_parameter`, `res.users.apikeys` |
| `admin_only` | Only admin users | `ir.model`, `ir.model.fields` |
| `admin_write_only` | Read OK for all, write needs admin | `res.company`, `res.currency` |

### Hardcoded Safety Guardrails

These cannot be overridden by YAML configuration:

**Blocked models** (32): `ir.config_parameter`, `res.users`, `res.users.apikeys`, `res.users.log`, `ir.cron`, `ir.module.module`, `ir.model.access`, `ir.rule`, `ir.mail_server`, `ir.ui.view`, `ir.actions.server`, `ir.logging`, `ir.attachment`, `ir.exports`, `ir.exports.line`, `iap.account`, `auth.totp.wizard`, `auth.totp.device`, `payment.token`, `payment.provider`, `base.automation`, `digest.digest`, `res.config.settings`, `change.password.wizard`, `change.password.user`, `base.module.update`, `base.module.upgrade`, `base.module.uninstall`, `fetchmail.server`, `bus.bus`, `mail.mail`, `mail.template`

**Read-only models** (8): `mail.message`, `mail.followers`, `mail.activity`, `discuss.channel`, `mail.notification`, `mail.compose.message`, `mail.alias`, `discuss.channel.member` (reads allowed, writes blocked for everyone)

**Blocked write fields** (10): `password`, `password_crypt`, `groups_id`, `totp_secret`, `signup_token`, `signup_type`, `signup_expiration`, `api_key`, `share`, `active`

**Blocked methods** (29): `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`, `name_create`, `load`, `import_data`, `export_data`, `flush_recordset`, `invalidate_recordset`, `_search_panel_select_range`, `_search_panel_select_multi_range`, `_search_panel_domain_image`, `_search`, `_read_progress_bar`

### Additional Security Features

- **Brute-force protection** — per-username lockout (5 fails → 5 min) AND per-source IP/connection lockout (30 fails → 15 min, blocks username-rotation attacks). Lockouts have fixed duration — cannot be extended by additional attempts (DoS-resistant).
- **Credential wrapper** — passwords/session IDs stored in a `Credential` class with leak-safe `__repr__`/`__str__`, explicit `.reveal()` for use, and `.clear()` on close
- **Server-side admin verification** — `is_admin` is re-verified via `has_group('base.group_system')` after authentication, defending against tampered auth responses
- **Private method guard** — underscore-prefixed methods (`_compute_*`, `_inverse_*`, etc.) blocked for everyone including admin unless explicitly whitelisted
- **Rate limiting** — per-session token bucket with separate global and write budgets
- **RBAC** — tool-level access control by user group, field-level response filtering, transparent drop reporting via `return_dropped=True`
- **Input validation** — model names, method names, field names, domain filters, ORDER BY clauses, groupby with temporal operators, 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 `defusedxml`
- **Domain validation** — Odoo domain filters validated for operators, field names, value types, nesting depth, and list sizes
- **Session-expiry retry** — JSON-RPC retries only on `OdooSessionExpiredError`, not generic auth errors (no double round-trips on access denials)

## Authentication

Three stock Odoo auth methods — no custom addon needed:

| Method | Protocol | Use Case |
|--------|----------|----------|
| `api_key` | XML-RPC | Server-to-server, CI/CD pipelines |
| `password` | JSON-RPC | Interactive users, Claude Desktop |
| `session` | JSON-RPC | Reuse existing browser session (development) |

```
# Example: login via the MCP tool
> login(method="password", username="admin", credential="admin", database="mydb")
```

## Core MCP Tools (13)

| Tool | Description |
|------|-------------|
| `login` | Authenticate with Odoo (api_key / password / session) |
| `list_models` | List accessible models with metadata and keyword filter |
| `get_model_fields` | Get field definitions for a model with optional filter |
| `search_read` | Search records with domain filters, field selection, ordering |
| `get_record` | Get a single record by ID |
| `search_count` | Count matching records |
| `create_record` | Create a new record (supports `dry_run` for validation-only) |
| `update_record` | Update existing record (supports `dry_run` for validation-only) |
| `delete_record` | Delete a single record by ID (supports `dry_run`) |
| `read_group` | Aggregated grouped reads with temporal operators (`date:month`, `date:quarter`, etc.) |
| `get_defaults` | Preview Odoo default values before `create_record` |
| `get_onchange` | Preview field side effects (with RBAC filtering) |
| `execute_method` | Call allowed model methods (supports `dry_run`) |

## Workflow Tools (2)

| Tool | Description |
|------|-------------|
| `get_create_requirements` | Get required fields and validation rules before creating a record |
| `get_record_actions` | Get available workflow actions for an existing record |

## MCP Resources (6)

| URI | Description |
|-----|-------------|
| `odoo://models` | List all accessible models |
| `odoo://models/{name}` | Model detail with field definitions |
| `odoo://record/{model}/{id}` | Single record data with RBAC field filtering |
| `odoo://schema/{model}` | Field schema with type info and importance ranking |
| `odoo://categories` | Model categories with counts |
| `odoo://workflow/{model}` | Workflow definition with stages and actions for a model |

## MCP Prompts (12)

| Prompt | Description |
|--------|-------------|
| `analyze_model` | Comprehensive model structure analysis |
| `explore_data` | Natural language data exploration guide |
| `create_workflow` | Guide through model-specific workflows |
| `compare_records` | Side-by-side record comparison |
| `generate_report` | Analytical report generation |
| `discover_custom_modules` | Find and understand custom modules |
| `debug_access` | Troubleshoot access and permission issues |
| `workflow_guide` | Step-by-step workflow execution guide for a model |
| `record_creation_guide` | Guided record creation with field validation |
| `bulk_operations` | Guide for performing bulk operations safely |
| `field_mapping` | Map fields between Odoo versions (v17/v18/v19) |
| `data_migration` | Guide for migrating data between models or versions |

## Built-in Domain Plugins

### HR Plugin
| Tool | Description |
|------|-------------|
| `check_in` | Record attendance check-in |
| `check_out` | Record attendance check-out |
| `get_my_attendance` | View attendance records (with month filter) |
| `get_my_leaves` | View leave requests (with state filter) |
| `request_leave` | Submit a leave request |
| `get_my_profile` | View employee profile |

### Sales Plugin
| Tool | Description |
|------|-------------|
| `get_my_quotations` | List quotations/orders (with state filter) |
| `get_order_details` | Full order details with line items |
| `confirm_order` | Confirm a draft/sent quotation |
| `get_sales_summary` | Aggregated sales statistics (with period filter) |

### Project Plugin
| Tool | Description |
|------|-------------|
| `get_my_tasks` | List assigned tasks (with state/project filter) |
| `get_project_summary` | Project stats: task counts by stage, overdue |
| `update_task_stage` | Move a task to a different stage |

### Helpdesk Plugin
| Tool | Description |
|------|-------------|
| `get_my_tickets` | List assigned tickets (with state/priority filter) |
| `create_ticket` | Create a new helpdesk ticket |
| `update_ticket_stage` | Move a ticket to a different stage |

## Custom Module Support

Add custom Odoo modules without writing Python code. Edit `model_access.yaml`:

```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_cancel
```

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

```python
from odoo_mcp_gateway.plugins.base import OdooPlugin

class ManufacturingPlugin(OdooPlugin):
    @property
    def nam

…

## Source & license

This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [parth-unjiya](https://github.com/parth-unjiya)
- **Source:** [parth-unjiya/odoo-mcp-gateway](https://github.com/parth-unjiya/odoo-mcp-gateway)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** yes
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/mcp-parth-unjiya-odoo-mcp-gateway
- Seller: https://agentstack.voostack.com/s/parth-unjiya
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
