Skip to content

Architecture

For the curious and for contributors. The authoritative source is the repository's docs/architecture.md.

Claude (MCP client)
      │  stdio (JSON-RPC MCP)
odoo_mcp.server  ──lists/dispatches──▶  registry (ToolRegistry)
      │                                     │
      │                                     ▼
      │                              tools/ (crud, meta, export, i18n, report)
      │                                     │  resolve_model / resolve_fields
      │                                     ▼
      │                              compat/ (detect → deltas → resolve)
      ▼                                     │
tenancy.ConnectionManager ──▶ session.OdooSession ──▶ transport/
   (per-tenant)                 (auth, facts, cache)     fallback → jsonrpc | xmlrpc
                                                        Odoo instance

Layers

  • transport/XmlRpcTransport, JsonRpcTransport and FallbackTransport (auto: JSON-RPC first, transparent fallback to XML-RPC on API key rejection or transport error; it pins XML-RPC after the first fallback and warns once).
  • session.pyOdooSession stores credentials, authenticates lazily (uid), caches the version/edition/deployment facts and wraps the schema reads in the SchemaCache TTL cache. ConnectionManager (tenancy.py) maps the tenant fingerprint (url|db|login, never the secret) to a session.
  • compat/detect.probe() builds EnvFacts; deltas.py is the declarative delta map; resolve.py converts a requested model/field/capability into whatever exists in the target. Each tool calls resolve_* before the ORM.
  • tools/ — thin handlers registered on the shared registry. The write tools carry read_only=False for future policy gates; the public core does not apply approval gates (that belongs to a private governance layer).
  • observability.py — optional Prometheus metrics and OTLP traces, both off by default and no-ops if the extras are not installed.
  • server.py / main.py — MCP stdio wiring and the odoo-mcp entry point. The handlers run on a worker thread (anyio.to_thread) so that a blocking RPC never stalls the event loop. The logs go to stderr; stdout is exclusive to the MCP channel.

Transports and modes

  • stdio (default): a single tenant, credentials from the environment. It is the mode used by the plugin's mcpServers entry.
  • HTTP (docker, optional/roadmap): multi-tenant via X-Odoo-Url/Db/Login headers + Bearer secret, resolved by tenancy.from_headers.

Design principles

  • A single tool surface across versions — the caller uses modern names.
  • Fail with remediationCompatError explains what to change; the unavailable fields degrade to warnings, not to hard failures.
  • Lightweight base install — the optional deps (cache/metrics/otel) never block startup.
  • No secrets in logs nor on disk.