oCoreoCore Docs

MCP Server

Configure, secure, and monitor the Model Context Protocol server for AI-assisted Odoo management.

The oCore MCP server enables AI assistants to interact with Odoo instances through a standardized protocol. This guide covers server architecture, configuration, security hardening, and monitoring for administrators.

Organization Settings

Manage MCP kill switch, API keys, and audit logs.

Open in Dashboard

Architecture

Transports

oCore exposes the MCP server over two transports:

TransportEndpointUse Case
SSE (Server-Sent Events)/api/mcp/sseLong-lived connections from desktop clients (Claude, Cursor)
Streamable HTTP/api/mcp/streamStateful HTTP sessions with idle timeout

Both transports share the same MCP server instance and tool registry. Authentication, RBAC, rate limiting, and audit logging apply identically across both.

Per-Session Tool Registry

Tools are not registered globally for all clients. Instead, when a session is established:

  1. The API key is authenticated and its permissions are loaded.
  2. Accessible instances are queried based on the key's organization and instance scope.
  3. Tools are registered on the session, namespaced by instance slug (e.g., prod_v17_search_read).
  4. Resources (model list, record access) are registered per instance.

This means each session sees only the tools for the instances it has access to. If an API key is scoped to two instances, the session gets tools for those two instances plus the global ocore_* platform tools.

Tool Naming Convention

Instance tools follow the pattern {normalized_slug}_{tool_suffix}:

  • Hyphens in slugs are replaced with underscores: prod-v17 becomes prod_v17
  • The slug ocore is reserved for platform tools and cannot be used as an instance slug
  • If two instances produce the same normalized slug, both receive an 8-character UUID suffix to disambiguate

Configuration

The MCP server is configured through the Config struct with the following defaults:

SettingDefaultDescription
MaxInstances20Maximum instances registered per session
RateLimitSSE100Maximum tool calls per minute per key (SSE transport)
RateLimitStdio30Maximum tool calls per minute per key (stdio transport)
KeepAliveInterval15 secondsSSE keep-alive ping interval
SessionIdleTTL30 minutesIdle timeout for Streamable HTTP sessions
TrustedProxyCIDRs(empty)CIDR ranges of reverse proxies whose X-Forwarded-For headers are trusted

Trusted Proxy Configuration

If oCore runs behind a reverse proxy (nginx, Caddy, cloud load balancer), configure TrustedProxyCIDRs so the MCP server extracts the real client IP from proxy headers rather than using the proxy's IP:

# Example: trust the local Docker network and Cloudflare ranges
TRUSTED_PROXY_CIDRS=172.17.0.0/16,173.245.48.0/20,103.21.244.0/22

When no trusted proxies are configured, X-Forwarded-For and X-Real-IP headers are ignored and the direct connection IP from RemoteAddr is used. This is the safe default.

Kill Switch

The organization-level MCP kill switch (mcp_enabled in organization settings) instantly disables all MCP access for the entire organization:

  • New sessions fail authentication
  • Existing sessions are blocked on the next tool call (revalidated every 30 seconds)
  • The toggle is checked both at session creation and periodically during tool calls

To toggle the kill switch, navigate to Settings > General and toggle the MCP Access switch, or use the API:

curl -X PUT https://ocore.example.com/api/org/settings \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"mcp_enabled": false}'

Kill Switch Is Organization-Wide

Disabling MCP access affects all API keys in the organization. Use per-key controls (pause, read-only, instance scoping) for more targeted restrictions.

API Key Scoping

MCP access control is configured per API key. Each key supports the following MCP-specific settings:

Instance Scoping

Restrict a key to specific instances by setting mcp_instance_ids. The key will only see tools for those instances. An empty list grants access to all running instances in the organization.

Project Scoping

Restrict a key to instances belonging to specific projects via mcp_project_ids.

Category Permissions

Set mcp_permissions to a list of tool categories the key can access:

CategoryTools
ormsearch_read, read, create, write, unlink, copy, execute_method, batch_*
searchadvanced_search, aggregation, distinct_field_values, fuzzy_search
metadatalist_models, get_fields, get_metadata, get_model_*, get_instance_info
sqlexecute_sql, execute_sql_script, list_tables
shellexecute_shell, execute_shell_multi, python_repl
filesfile_browse, file_read, file_write, file_create, file_delete, file_tree, search_in_code
modulesinstall_module, uninstall_module, upgrade_module, list_modules, module_*
systemlist_users, user_groups, set_user_*, server_info, odoo_logs, scheduled actions
platformocore_list_instances, ocore_list_servers

An empty permissions list means unrestricted access to all categories.

Read-Only Mode

Set mcp_read_only: true to block all write and destructive operations. The key can still search, read, and inspect schemas.

Active/Pause Toggle

Set mcp_active: false to pause a key without revoking it. Paused keys receive a clear error on every tool call. Resume by setting mcp_active: true.

Tool Allow/Deny Lists

Fine-grained per-tool filtering with mcp_allowlist_mode and mcp_tool_allowlist:

  • allow mode: Only tools whose suffix is in the list are permitted
  • deny mode: Tools whose suffix is in the list are blocked; all others are allowed
  • none mode (default): No per-tool filtering

IP Allowlist

Set ip_allowlist to restrict connections to specific IP addresses or CIDR ranges. Connections from non-listed IPs are rejected.

Production Guard

Each Odoo instance has a mcp_write_enabled boolean flag (default: false). When disabled, all write and destructive tool calls targeting that instance are blocked, regardless of the API key's permissions.

This provides a safety net for production instances:

  • Enable writes only on development and staging instances
  • Temporarily enable writes on production for specific maintenance windows
  • The flag is checked on every tool call with a fresh DB query when the session cache says blocked

To toggle the production guard:

curl -X PUT https://ocore.example.com/api/instances/{instanceId} \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"mcp_write_enabled": true}'

Token Revalidation

API keys are not validated only at session creation. The MCP server periodically revalidates keys during active sessions:

  • Interval: Every 30 seconds per key
  • Checks performed: Key existence, expiration, owning user active status, mcp_active flag, organization kill switch
  • Fail-open on transient errors: If the database is temporarily unreachable, the call proceeds to avoid breaking sessions on brief outages
  • Immediate eviction: When a key is confirmed revoked, expired, or its user is deactivated, the next tool call fails immediately

Call InvalidateAuthCache(keyID) programmatically to force immediate revalidation (e.g., when pausing or revoking a key through the UI).

Monitoring

Health Endpoint

The MCP health endpoint is available at /api/mcp/health and returns basic server status:

curl https://ocore.example.com/api/mcp/health
{
  "status": "ok",
  "server_name": "oCore — Odoo Platform Manager",
  "version": "1.0.0"
}

This endpoint is unauthenticated and does not expose operational details.

Session Tracking

Active MCP sessions are tracked via an atomic counter. The current session count is available through the authenticated dashboard stats endpoint.

Audit Logs

All MCP tool calls are written to the mcp_audit_log table. Each entry records:

FieldDescription
organization_idOrganization the key belongs to
user_idUser who owns the API key
api_key_idThe API key used
session_idMCP session identifier
tool_nameFull tool name (e.g., prod_v17_search_read)
tool_categoryRBAC category (e.g., orm, sql)
input_paramsRequest parameters (sensitive values redacted)
result_summaryTruncated result (first 500 characters)
result_bytesTotal response size in bytes
is_errorWhether the tool call failed
error_messageError text (if applicable)
latency_msExecution time in milliseconds
ip_addressClient IP address
instance_idTarget instance (null for platform tools)

High-level events (session start/end) are logged to the main audit_log table with action types mcp_session_started and mcp_session_ended.

Usage Stats

Query MCP usage statistics per API key:

# Get usage stats for a specific key
curl "https://ocore.example.com/api/org/mcp/key-stats/{keyId}" \
  -H "Authorization: Bearer $TOKEN"

Security Hardening Checklist

Enable the kill switch by default. Only enable MCP access when actively using AI tools. Toggle it off when not in use.

Scope API keys to specific instances. Never use an unscoped key in production. Create separate keys for production (read-only) and development (read-write).

Set read-only mode for monitoring keys. Keys used for dashboards, reporting, or read-heavy workflows should be marked mcp_read_only: true.

Use category restrictions. If a key only needs to inspect schemas, grant only metadata permission. If it only needs ORM access, grant only orm.

Configure IP allowlists. Restrict keys to known IP ranges, especially for production instance access.

Keep mcp_write_enabled off for production instances. Only enable it during planned maintenance windows and disable it immediately after.

Configure trusted proxy CIDRs. If oCore runs behind a reverse proxy, set TrustedProxyCIDRs so IP-based access controls work correctly.

Set key expiration dates. Avoid indefinite keys. Use expiration dates and rotate keys regularly.

Monitor audit logs. Review mcp_audit_log entries regularly for unexpected tool calls, high error rates, or unfamiliar IP addresses.

Use per-tool allow/deny lists. For high-security environments, use allowlist mode to explicitly permit only the tools each key needs.

Rate Limiting

Per-key rate limiting prevents any single API key from overwhelming the server. Limits are enforced per minute using a token bucket algorithm:

TransportDefault Limit
SSE100 calls/minute
Stdio30 calls/minute

When a key exceeds its limit, tool calls return a rate limit exceeded error until the window resets. Rate limiter state is in-memory and automatically cleaned up for keys idle longer than 30 minutes.

Troubleshooting

MCP sessions not connecting

  • Check that mcp_enabled is true in organization settings
  • Verify the API key has not expired or been revoked
  • Check that the owning user account is active
  • Verify the SSE or Streamable HTTP endpoint is reachable (not blocked by firewall/WAF)

No tools appearing in session

  • Confirm at least one instance has status: running in the organization
  • Check that the API key is not scoped to instances that are stopped or deleted
  • Verify the key's mcp_active flag is true

Writes blocked unexpectedly

  • Check the instance's mcp_write_enabled flag
  • Check the API key's mcp_read_only flag
  • Verify the key's category permissions include write-capable categories
  • For destructive operations, ensure confirm: true is in the tool arguments
  • Verify the client IP is in the key's ip_allowlist
  • If behind a proxy, ensure TrustedProxyCIDRs is configured so the real client IP is extracted
  • Check the audit log for the rejected IP address

High latency on tool calls

  • Each tool call involves an SSH connection to the server and a docker exec into the Odoo container, adding approximately 2-3 seconds of baseline latency
  • Complex ORM operations or large result sets add additional time
  • Use timeout_seconds and max_response_size parameters to bound execution time and response size

OAuth 2.1 Authorization Server

oCore includes a full OAuth 2.1 Authorization Server that enables AI clients like Claude Desktop and ChatGPT to connect to the MCP server without manually copying API keys. Clients discover oCore through standard protocol endpoints, authenticate via the authorization code flow with PKCE, and receive scoped MCP tokens.

Discovery Endpoints

MCP clients use these well-known endpoints to discover the authorization server and its capabilities:

EndpointRFCPurpose
/.well-known/oauth-protected-resourceRFC 9728Tells clients which authorization server protects the MCP resource
/.well-known/oauth-authorization-serverRFC 8414Full metadata: endpoints, supported grant types, PKCE methods
/.well-known/openid-configurationOIDC DiscoverySame as above, plus OIDC-specific fields (subject_types_supported, id_token_signing_alg_values_supported)
/.well-known/jwks.jsonRFC 7517Public keys for verifying ID tokens (RS256)

OAuth Endpoints

EndpointMethodPurpose
/oauth/authorizeGETAuthorization endpoint (redirects to login/consent)
/oauth/tokenPOSTToken exchange (authorization code, refresh token, device code)
/oauth/registerPOSTDynamic Client Registration (RFC 7591)
/oauth/introspectPOSTToken introspection (RFC 7662, confidential clients only)
/oauth/revokePOSTToken revocation (RFC 7009)
/oauth/parPOSTPushed Authorization Request (RFC 9126)
/oauth/devicePOSTDevice Authorization Grant (RFC 8628)
/oauth/device/verifyGET/POSTDevice code verification (user-facing)
/oauth/consentGET/POSTConsent data and submission
/oauth/userinfoGET/POSTOIDC UserInfo

How Claude Desktop and ChatGPT Connect

When an MCP-compatible AI client connects to oCore:

The client fetches /.well-known/oauth-protected-resource from the MCP server URL to discover the authorization server.

The client fetches /.well-known/oauth-authorization-server to learn the authorization, token, and registration endpoints.

The client dynamically registers itself via POST /oauth/register (if not already registered), receiving a client_id.

The client initiates the authorization code flow with PKCE (code_challenge_method=S256), opening the user's browser to /oauth/authorize.

The user logs into oCore (if not already authenticated), reviews the consent screen, selects an organization, and approves.

oCore redirects back to the client with an authorization code, which the client exchanges for access and refresh tokens at /oauth/token.

Clients may also use CIMD (Client ID Metadata Document) instead of dynamic registration. If the client_id is an HTTPS URL, oCore fetches the metadata document from that URL to resolve client information.

Dynamic Client Registration

Any MCP client can register itself at runtime via POST /oauth/register:

curl -X POST https://ocore.example.com/oauth/register \
  -H "Content-Type: application/json" \
  -d '{
    "client_name": "My AI Tool",
    "redirect_uris": ["https://myapp.example.com/callback"],
    "grant_types": ["authorization_code", "refresh_token"],
    "token_endpoint_auth_method": "none"
  }'

The server returns a client_id (and client_secret for confidential clients using client_secret_post). Registration is rate-limited to 10 requests per IP per minute.

Supported token_endpoint_auth_method values:

MethodTypeDescription
nonePublicNo client secret. Used by desktop apps and CLI tools.
client_secret_postConfidentialClient secret sent in POST body. Used by server-side integrations.

Device Authorization Grant

For CLI tools and environments without a browser redirect, oCore supports RFC 8628 Device Authorization Grant:

The CLI tool sends POST /oauth/device with its client_id. oCore returns a user_code (8 consonants, formatted XXXX-XXXX) and a verification_uri.

The tool displays the user code and verification URL to the operator. A QR code (base64-encoded PNG) is also returned for mobile scanning.

The operator opens the verification URL in a browser, logs in, and enters or confirms the user code. They select an organization and approve.

The CLI tool polls POST /oauth/token with grant_type=urn:ietf:params:oauth:grant-type:device_code. Polling interval starts at 5 seconds; the server responds with slow_down if polled too frequently.

Once approved, the token endpoint returns access and refresh tokens. Device codes expire after 15 minutes.

CORS Configuration

OAuth endpoints use split CORS configurations to balance security with compatibility:

Endpoint GroupOriginsCredentialsRationale
Public (token, register, introspect, revoke, .well-known/*)* (wildcard)falseCalled by external OAuth clients; no cookies needed
Session (authorize, consent, par, device/verify)APP_URL onlytrueLoaded by the oCore frontend; requires session cookies

Public endpoints accept Authorization and Content-Type headers. Session endpoints additionally accept X-CSRF-Token.

Security Headers

All /oauth/* responses include these security headers per RFC 6749 Section 5.1:

Cache-Control: no-store
Pragma: no-cache
X-Content-Type-Options: nosniff

The consent endpoint also sets Referrer-Policy: no-referrer to prevent the request_id from leaking in the Referer header.

JWKS Key Rotation

oCore automatically manages RSA-2048 signing keys for OIDC ID tokens:

  • A signing key is generated on first startup if none exists
  • Keys are checked hourly and rotated after 90 days
  • Rotated keys remain in the JWKS response for 180 days for in-flight token verification
  • The JWKS endpoint (/.well-known/jwks.json) returns all active and recently-rotated public keys
  • JWKS responses are cached with Cache-Control: public, max-age=3600
Was this page helpful?