{
  "markdown": "# odoo-mcp-gateway\n\nSecurity-first, version-agnostic MCP gateway for Odoo 17/18/19. Works with stock and custom modules via YAML configuration. Zero Odoo-side code required.\n\n[![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/)\n[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)\n[![Odoo](https://img.shields.io/badge/Odoo-17%20%7C%2018%20%7C%2019-714B67.svg)](https://www.odoo.com/)\n[![Tests](https://img.shields.io/badge/tests-1476%20passing-brightgreen.svg)](#testing)\n[![Coverage](https://img.shields.io/badge/coverage-93%25-brightgreen.svg)](#testing)\n\n<!-- mcp-name: io.github.parth-unjiya/odoo-mcp-gateway -->\n\n## 30-Second Quick Start\n\n```bash\npip install odoo-mcp-gateway\n```\n\nAdd to Claude Desktop config (`claude_desktop_config.json`):\n\n```json\n{\n  \"mcpServers\": {\n    \"odoo\": {\n      \"command\": \"python\",\n      \"args\": [\"-m\", \"odoo_mcp_gateway\"],\n      \"env\": {\n        \"ODOO_URL\": \"http://localhost:8069\",\n        \"ODOO_DB\": \"your_database\"\n      }\n    }\n  }\n}\n```\n\nRestart Claude Desktop. In any conversation:\n\n```\nlogin with method \"password\", username \"admin\", credential \"your_password\"\n```\n\nYou'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.\n\n**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).\n\n## What's New in v0.2.1\n\n- **Brute-force protection** — per-username (5/5min) + per-source (30/15min) lockout\n- **`dry_run` mode** on `create_record`, `update_record`, `delete_record`, `execute_method` — validate without executing\n- **2 new tools**: `get_defaults` (preview Odoo defaults), `get_onchange` (preview field side effects)\n- **Temporal grouping** in `read_group`: `create_date:month`, `date:quarter`, etc.\n- **Hardened blocklists**: 32 always-blocked models (was 17), 29 always-blocked methods (was 18), 10 always-blocked write fields, 8 always read-only models\n- **Server-side admin verification** via `has_group('base.group_system')` (was trusted from auth response)\n- **Credential wrapper** prevents password leakage via `repr()`/traceback\n- **JSON-RPC retry** only fires on `OdooSessionExpiredError` (was retrying on every auth error)\n\nSee [CHANGELOG.md](CHANGELOG.md) for the full list of 21 security fixes.\n\n## Why This Exists\n\nExisting 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:\n\n- **Two-layer security** — MCP restrictions (YAML) + Odoo's built-in ACLs (ir.model.access + ir.rule)\n- **YAML-driven configuration** — model restrictions, RBAC, field-level access, rate limiting, audit logging\n- **Custom module support** — auto-discovers models via `ir.model`, add YAML config and it works\n- **Version-agnostic** — Odoo 17, 18, 19 with version-specific adapters\n- **Zero Odoo-side code** — `pip install` + YAML config = done. No custom addon required\n- **Full MCP primitives** — 31 Tools + 6 Resources + 12 Prompts (most servers only implement Tools)\n- **Plugin architecture** — extend with pip-installable domain packs via entry_points\n\n## Architecture\n\n```\nMCP Client (Claude Desktop / Claude Code / HTTP)\n    |  User calls login tool with Odoo credentials\n    v\nMCP Server (FastMCP)\n    |\n    |-- security_gate()    --> Rate limit + RBAC tool access + audit logging\n    |-- restrictions       --> Model/method/field block lists (YAML + hardcoded)\n    |-- rbac               --> Field-level filtering + write sanitization\n    |\n    |-- tools/             --> 31 MCP tools (auth + schema + CRUD + workflow + plugins)\n    |-- resources/         --> 6 MCP resources (odoo:// URIs)\n    |-- prompts/           --> 12 reusable prompt templates\n    |-- plugins/           --> Entry-point plugin system (HR, Sales, Project, Helpdesk)\n    |\n    |  JSON-RPC / XML-RPC as authenticated user\n    v\nOdoo 17/18/19 (security enforced per user via ir.model.access + ir.rule)\n```\n\n### Security Pipeline\n\nEvery tool and resource call passes through this pipeline:\n\n```\nRequest --> Rate Limit --> Authentication Check --> RBAC Tool Access\n    --> Model Restriction --> Method Restriction --> Field Validation\n    --> Handler Execution --> RBAC Field Filtering --> Audit Log --> Response\n```\n\nHardcoded safety guardrails that cannot be overridden by YAML:\n- **32 always-blocked models** — system internals, auth/TOTP, payment tokens, attachments, mail.mail, base.automation, and more\n- **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)\n- **10 always-blocked write fields** — password, password_crypt, groups_id, totp_secret, signup_token/type/expiration, api_key, share, active\n- **29 always-blocked methods** — sudo, with_user/env/context, _sql, _write, _create, name_create, load, import_data, export_data, and more\n- **28 ORM methods blocked in execute_method** (prevents bypassing field-level checks)\n- **Per-username brute-force lockout** — 5 failures → 5 minute lockout (fixed duration, cannot be extended)\n- **Per-source brute-force lockout** — 30 failures / 15 min, prevents username-rotation attacks\n- **Credential wrapper class** — passwords stored with leak-safe `__repr__`/`__str__`, cleared on close\n- **Server-side admin verification** — `has_group('base.group_system')` overrides auth-response `is_admin`\n\n## Quick Start\n\n```bash\npip install odoo-mcp-gateway\n\n# Copy and edit config files\ncp config/restrictions.yaml.example config/restrictions.yaml\ncp config/model_access.yaml.example config/model_access.yaml\ncp config/rbac.yaml.example config/rbac.yaml\n\n# Set environment variables\nexport ODOO_URL=http://localhost:8069\nexport ODOO_DB=mydb\n\n# Run (stdio mode for Claude Desktop / Claude Code)\npython -m odoo_mcp_gateway\n\n# Or HTTP mode for web clients\nMCP_TRANSPORT=streamable-http python -m odoo_mcp_gateway\n```\n\n### Claude Desktop Configuration\n\nAdd to `claude_desktop_config.json`:\n\n```json\n{\n  \"mcpServers\": {\n    \"odoo\": {\n      \"command\": \"python\",\n      \"args\": [\"-m\", \"odoo_mcp_gateway\"],\n      \"env\": {\n        \"ODOO_URL\": \"http://localhost:8069\",\n        \"ODOO_DB\": \"mydb\"\n      }\n    }\n  }\n}\n```\n\n### Claude Code Configuration\n\n```bash\n# Add as MCP server\nclaude mcp add odoo -- python -m odoo_mcp_gateway\n```\n\n### Environment Variables\n\n| Variable | Default | Description |\n|----------|---------|-------------|\n| `ODOO_URL` | `http://localhost:8069` | Odoo server URL |\n| `ODOO_DB` | *(required)* | Odoo database name |\n| `MCP_TRANSPORT` | `stdio` | Transport mode (`stdio` or `streamable-http`) |\n| `MCP_HOST` | `127.0.0.1` | HTTP host (streamable-http mode) |\n| `MCP_PORT` | `8080` | HTTP port (streamable-http mode) |\n| `MCP_LOG_LEVEL` | `INFO` | Logging level |\n| `CONFIG_DIR` | `.` | Directory for YAML config files |\n| `SESSION_TIMEOUT_SECONDS` | `1800` | Session inactivity timeout |\n| `MAX_CONCURRENT_SESSIONS` | `100` | Maximum concurrent sessions |\n| `RATE_LIMIT_GLOBAL` | `60` | Requests per minute (global) |\n| `RATE_LIMIT_WRITE` | `20` | Write operations per minute |\n\n## Security\n\n### Two-Layer Security Model\n\n1. **MCP gateway restrictions** (YAML config + hardcoded guardrails) — blocks sensitive models, dangerous methods, privileged fields *before* any Odoo call is made\n2. **Odoo's built-in ACLs** — enforces per-user access on actual records via `ir.model.access` and `ir.rule`\n\n### Model Restriction Tiers\n\n| Tier | Effect | Example |\n|------|--------|---------|\n| `always_blocked` | Nobody can access, including admins | `ir.config_parameter`, `res.users.apikeys` |\n| `admin_only` | Only admin users | `ir.model`, `ir.model.fields` |\n| `admin_write_only` | Read OK for all, write needs admin | `res.company`, `res.currency` |\n\n### Hardcoded Safety Guardrails\n\nThese cannot be overridden by YAML configuration:\n\n**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`\n\n**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)\n\n**Blocked write fields** (10): `password`, `password_crypt`, `groups_id`, `totp_secret`, `signup_token`, `signup_type`, `signup_expiration`, `api_key`, `share`, `active`\n\n**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`\n\n### Additional Security Features\n\n- **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).\n- **Credential wrapper** — passwords/session IDs stored in a `Credential` class with leak-safe `__repr__`/`__str__`, explicit `.reveal()` for use, and `.clear()` on close\n- **Server-side admin verification** — `is_admin` is re-verified via `has_group('base.group_system')` after authentication, defending against tampered auth responses\n- **Private method guard** — underscore-prefixed methods (`_compute_*`, `_inverse_*`, etc.) blocked for everyone including admin unless explicitly whitelisted\n- **Rate limiting** — per-session token bucket with separate global and write budgets\n- **RBAC** — tool-level access control by user group, field-level response filtering, transparent drop reporting via `return_dropped=True`\n- **Input validation** — model names, method names, field names, domain filters, ORDER BY clauses, groupby with temporal operators, write values (size/depth/type)\n- **IDOR protection** — plugin tools scope data access to the authenticated user\n- **Audit logging** — structured JSON logs for all allowed and denied operations\n- **Error sanitization** — strips internal URLs, SQL fragments, file paths, stack traces from error messages\n- **XXE protection** — XML-RPC responses parsed with `defusedxml`\n- **Domain validation** — Odoo domain filters validated for operators, field names, value types, nesting depth, and list sizes\n- **Session-expiry retry** — JSON-RPC retries only on `OdooSessionExpiredError`, not generic auth errors (no double round-trips on access denials)\n\n## Authentication\n\nThree stock Odoo auth methods — no custom addon needed:\n\n| Method | Protocol | Use Case |\n|--------|----------|----------|\n| `api_key` | XML-RPC | Server-to-server, CI/CD pipelines |\n| `password` | JSON-RPC | Interactive users, Claude Desktop |\n| `session` | JSON-RPC | Reuse existing browser session (development) |\n\n```\n# Example: login via the MCP tool\n> login(method=\"password\", username=\"admin\", credential=\"admin\", database=\"mydb\")\n```\n\n## Core MCP Tools (13)\n\n| Tool | Description |\n|------|-------------|\n| `login` | Authenticate with Odoo (api_key / password / session) |\n| `list_models` | List accessible models with metadata and keyword filter |\n| `get_model_fields` | Get field definitions for a model with optional filter |\n| `search_read` | Search records with domain filters, field selection, ordering |\n| `get_record` | Get a single record by ID |\n| `search_count` | Count matching records |\n| `create_record` | Create a new record (supports `dry_run` for validation-only) |\n| `update_record` | Update existing record (supports `dry_run` for validation-only) |\n| `delete_record` | Delete a single record by ID (supports `dry_run`) |\n| `read_group` | Aggregated grouped reads with temporal operators (`date:month`, `date:quarter`, etc.) |\n| `get_defaults` | Preview Odoo default values before `create_record` |\n| `get_onchange` | Preview field side effects (with RBAC filtering) |\n| `execute_method` | Call allowed model methods (supports `dry_run`) |\n\n## Workflow Tools (2)\n\n| Tool | Description |\n|------|-------------|\n| `get_create_requirements` | Get required fields and validation rules before creating a record |\n| `get_record_actions` | Get available workflow actions for an existing record |\n\n## MCP Resources (6)\n\n| URI | Description |\n|-----|-------------|\n| `odoo://models` | List all accessible models |\n| `odoo://models/{name}` | Model detail with field definitions |\n| `odoo://record/{model}/{id}` | Single record data with RBAC field filtering |\n| `odoo://schema/{model}` | Field schema with type info and importance ranking |\n| `odoo://categories` | Model categories with counts |\n| `odoo://workflow/{model}` | Workflow definition with stages and actions for a model |\n\n## MCP Prompts (12)\n\n| Prompt | Description |\n|--------|-------------|\n| `analyze_model` | Comprehensive model structure analysis |\n| `explore_data` | Natural language data exploration guide |\n| `create_workflow` | Guide through model-specific workflows |\n| `compare_records` | Side-by-side record comparison |\n| `generate_report` | Analytical report generation |\n| `discover_custom_modules` | Find and understand custom modules |\n| `debug_access` | Troubleshoot access and permission issues |\n| `workflow_guide` | Step-by-step workflow execution guide for a model |\n| `record_creation_guide` | Guided record creation with field validation |\n| `bulk_operations` | Guide for performing bulk operations safely |\n| `field_mapping` | Map fields between Odoo versions (v17/v18/v19) |\n| `data_migration` | Guide for migrating data between models or versions |\n\n## Built-in Domain Plugins\n\n### HR Plugin\n| Tool | Description |\n|------|-------------|\n| `check_in` | Record attendance check-in |\n| `check_out` | Record attendance check-out |\n| `get_my_attendance` | View attendance records (with month filter) |\n| `get_my_leaves` | View leave requests (with state filter) |\n| `request_leave` | Submit a leave request |\n| `get_my_profile` | View employee profile |\n\n### Sales Plugin\n| Tool | Description |\n|------|-------------|\n| `get_my_quotations` | List quotations/orders (with state filter) |\n| `get_order_details` | Full order details with line items |\n| `confirm_order` | Confirm a draft/sent quotation |\n| `get_sales_summary` | Aggregated sales statistics (with period filter) |\n\n### Project Plugin\n| Tool | Description |\n|------|-------------|\n| `get_my_tasks` | List assigned tasks (with state/project filter) |\n| `get_project_summary` | Project stats: task counts by stage, overdue |\n| `update_task_stage` | Move a task to a different stage |\n\n### Helpdesk Plugin\n| Tool | Description |\n|------|-------------|\n| `get_my_tickets` | List assigned tickets (with state/priority filter) |\n| `create_ticket` | Create a new helpdesk ticket |\n| `update_ticket_stage` | Move a ticket to a different stage |\n\n## Custom Module Support\n\nAdd custom Odoo modules without writing Python code. Edit `model_access.yaml`:\n\n```yaml\ncustom_models:\n  full_crud:\n    - custom.delivery.route\n    - custom.warehouse.zone\n  read_only:\n    - custom.delivery.log\n\nallowed_methods:\n  custom.delivery.route:\n    - action_dispatch\n    - action_complete\n    - action_cancel\n```\n\nThen all CRUD tools (`search_read`, `create_record`, `update_record`, `delete_record`) and `execute_method` work on the custom models with full security enforcement.\n\n## Plugin System\n\nExtend the gateway with pip-installable plugins:\n\n```python\nfrom odoo_mcp_gateway.plugins.base import OdooPlugin\n\nclass ManufacturingPlugin(OdooPlugin):\n    @property\n    def name(self) -> str:\n        return \"manufacturing\"\n\n    @property\n    def required_odoo_modules(self) -> list[str]:\n        return [\"mrp\"]\n\n    @property\n    def required_models(self) -> list[str]:\n        return [\"mrp.production\", \"mrp.bom\"]\n\n    def register(self, server, context):\n        @server.tool()\n        async def get_production_orders(...):\n            ...\n```\n\nRegister via `pyproject.toml` entry points:\n\n```toml\n[project.entry-points.\"odoo_mcp_gateway.plugins\"]\nmanufacturing = \"my_package:ManufacturingPlugin\"\n```\n\n## Configuration Files\n\n| File | Purpose |\n|------|---------|\n| `config/restrictions.yaml` | Model/method/field block lists (3 tiers) |\n| `config/model_access.yaml` | Per-model access policies, allowed methods, sensitive fields |\n| `config/rbac.yaml` | Role-based tool access and field filtering by group |\n| `config/gateway.yaml` | Server, connection, auth settings |\n\nAll files have `.example` templates with extensive inline documentation. Copy and customize:\n\n```bash\ncp config/restrictions.yaml.example config/restrictions.yaml\ncp config/model_access.yaml.example config/model_access.yaml\ncp config/rbac.yaml.example config/rbac.yaml\n```\n\n### Example: Restrict a Model\n\n```yaml\n# restrictions.yaml\nrestrictions:\n  always_blocked:\n    - my.secret.model\n  admin_only:\n    - hr.salary.rule\n  admin_write_only:\n    - res.company\n  blocked_write_fields:\n    - password_crypt\n    - api_key\n    - totp_secret\n```\n\n### Example: RBAC by Group\n\n```yaml\n# rbac.yaml\nrbac:\n  tool_group_requirements:\n    delete_record:\n      - base.group_system\n    execute_method:\n      - base.group_erp_manager\n  sensitive_fields:\n    hr.employee:\n      salary:\n        required_group: hr.group_hr_manager\n```\n\n## Docker\n\n```bash\ncp .env.example .env   # Edit with your Odoo settings\ndocker compose up\n```\n\nServices:\n- **MCP Gateway** — port 8080 (streamable-http mode)\n- **Odoo 18** — internal only (no host port exposed by default)\n- **PostgreSQL** — internal only\n\nThe gateway runs as a non-root user in a minimal Python image.\n\n## CLI Tools\n\n```bash\n# Test Odoo connectivity\nodoo-mcp-tools test-connection --url http://localhost:8069\n\n# Validate all YAML config files\nodoo-mcp-tools validate-config --config-dir config\n\n# List configured model access policies\nodoo-mcp-tools list-models --config-dir config\n```\n\n## Development\n\n```bash\ngit clone https://github.com/parth-unjiya/odoo-mcp-gateway.git\ncd odoo-mcp-gateway\npip install -e \".[dev]\"\n\n# Run tests\npytest tests/ -v\n\n# Run with coverage\npytest tests/ --cov=odoo_mcp_gateway --cov-report=term-missing\n\n# Lint\nruff check src/ tests/\n\n# Type check (strict mode)\nmypy src/\n```\n\n### Source Layout\n\n```\nsrc/odoo_mcp_gateway/\n├── __main__.py                  # Entry point (stdio + HTTP)\n├── server.py                    # FastMCP server setup, tool registration\n├── config.py                    # Pydantic settings (env + .env)\n├── client/\n│   ├── base.py                  # OdooClientBase ABC, AuthResult\n│   ├── jsonrpc.py               # JSON-RPC client (session auth)\n│   ├── xmlrpc.py                # XML-RPC client (API key auth, defusedxml)\n│   └── exceptions.py            # OdooError hierarchy (7 types)\n├── core/\n│   ├── auth/manager.py          # 3 auth strategies\n│   ├── connection/manager.py    # Circuit breaker + retry\n│   ├── version/                 # Odoo 17/18/19 detection + adapters\n│   ├── workflow/\n│   │   ├── definitions.py      # WorkflowDef, StateDef, TransitionDef dataclasses\n│   │   ├── registry.py         # Workflow registration and lookup\n│   │   └── stock_workflows/    # Built-in workflows (sale, purchase, HR, etc.)\n│   ├── security/\n│   │   ├── restrictions.py      # 3-tier model/method restrictions + hardcoded guardrails\n│   │   ├── rbac.py              # Tool access + field filtering\n│   │   ├── middleware.py        # Security pipeline + security_gate()\n│   │   ├── rate_limit.py        # Token bucket rate limiter\n│   │   ├── audit.py             # Structured audit logging\n│   │   ├── sanitizer.py         # Error message sanitization\n│   │   └── config_loader.py     # YAML config → Pydantic models\n│   └── discovery/\n│       ├── model_registry.py    # ir.model auto-discovery\n│       ├── field_inspector.py   # fields_get with TTL cache\n│       └── suggestions.py       # Category search + related models\n├── tools/\n│   ├── auth.py                  # login tool\n│   ├── schema.py                # list_models, get_model_fields\n│   ├── crud.py                  # search_read, create/update/delete, execute_method\n│   └── workflow.py              # get_create_requirements, get_record_actions\n├── resources/handlers.py        # 6 MCP resources (odoo:// URIs)\n├── prompts/handlers.py          # 12 MCP prompt templates\n├── plugins/\n│   ├── base.py, registry.py     # Plugin ABC + entry_point discovery\n│   └── core/                    # Built-in plugins (HR, Sales, Project, Helpdesk)\n├── cli/tools.py                 # CLI: test-connection, validate-config\n└── utils/                       # Domain builder, formatting, token budget\n```\n\n## Testing\n\n**1,476 tests passing, 93% code coverage**, mypy strict clean, ruff clean:\n\n```\ntests/unit/\n├── client/          # JSON-RPC, XML-RPC, auth manager, XXE protection\n├── security/        # Restrictions, RBAC, audit, rate limit, sanitizer, security_gate\n├── discovery/       # Model registry, field inspector, suggestions\n├── tools/           # All 13 MCP tools + input validation + dry_run\n├── plugins/         # Plugin system + 4 domain plugins + IDOR protection\n└── cli/             # CLI utility tools\n```\n\n```bash\n# Run all tests\npytest tests/ -v\n\n# Run specific area\npytest tests/unit/security/ -v\npytest tests/unit/tools/ -v\npytest tests/unit/plugins/ -v\n\n# Coverage report\npytest tests/ --cov=odoo_mcp_gateway --cov-report=html\n```\n\n## Error Handling\n\nAll Odoo errors are classified into 7 types:\n\n| Error | Cause |\n|-------|-------|\n| `OdooConnectionError` | Cannot reach Odoo server |\n| `OdooAuthError` | Invalid credentials |\n| `OdooAccessError` | ir.model.access denied |\n| `OdooValidationError` | Field validation failure |\n| `OdooUserError` | Business logic error |\n| `OdooMissingError` | Record not found |\n| `OdooVersionError` | Unsupported Odoo version |\n\nAll error messages are sanitized before reaching the MCP client — internal URLs, SQL fragments, file paths, and stack traces are automatically stripped.\n\n## Known Limitations\n\n- **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. (Note: passwords are stored in a `Credential` wrapper that prevents `repr()`/traceback leakage.)\n- **HTTP mode session isolation**: `streamable-http` transport currently has known session isolation limitations — the `_current_session_key` ContextVar is set inside the login tool but subsequent tool calls from different request contexts may fall back to the first available session. **Deploy HTTP mode as single-tenant only** (one user per server process) until per-request middleware lands in v0.3.0. stdio mode is single-session by design and unaffected.\n\n## Contributing\n\n1. Fork the repository\n2. Create a feature branch (`git checkout -b feature/my-feature`)\n3. Make your changes with tests\n4. Ensure all checks pass: `pytest && ruff check src/ tests/ && mypy src/`\n5. Submit a pull request\n\n## License\n\n[MIT](LICENSE)\n",
  "bytes": 23825,
  "sha": "54e5b0fe105f7920d938b35eaa4534655556117c722fe049bb8ae833bb31c01e",
  "repo_slug": "parth-unjiya/odoo-mcp-gateway",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_parth_unjiya_odoo_mcp_gateway_d851206e/readme"
}