Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
048ee49
[ADD] ai_oca_mcp
etobella Apr 20, 2026
4745c66
[UPD] Update ai_oca_mcp.pot
May 15, 2026
0f20d6d
[BOT] post-merge updates
OCA-git-bot May 15, 2026
fb94cd7
Added translation using Weblate (Italian)
mymage May 19, 2026
e11c62f
Translated using Weblate (Italian)
mymage May 29, 2026
f191757
Added translation using Weblate (Portuguese (Brazil))
felipemotter Jun 15, 2026
d762058
Translated using Weblate (Portuguese (Brazil))
felipemotter Jun 15, 2026
7d60cd5
Added translation using Weblate (Galician)
dnc-smc Jul 6, 2026
4477f21
Translated using Weblate (Galician)
dnc-smc Jul 6, 2026
72630b5
[FIX] ai_oca_mcp: grant internal users access to their own MCP keys
nobuQuartile Jul 15, 2026
d51f07c
[BOT] post-merge updates
OCA-git-bot Oct 1, 2026
454bcd3
[IMP] ai_oca_mcp: pre-commit auto fixes
petrus-v Jun 29, 2026
5961539
[MIG] ai_oca_mcp: Migrate to v17
petrus-v Jun 29, 2026
597849f
[IMP] ai_oca_mcp: improve coverage
petrus-v Jun 29, 2026
6fdc9c2
[REF] ai_oca_mcp: rename server variable to mcp_server_key
petrus-v Jun 29, 2026
c61de5c
[REF] ai_oca_mcp: remove listChanged capabilites
petrus-v Jun 29, 2026
26853b0
[IMP] ai_oca_mcp: pre-commit auto fixes
angelmoya Jun 5, 2026
60a56d6
[MIG] ai_oca_mcp: Migration to 18.0
angelmoya Jun 5, 2026
f1da1d5
ai_tool: revamp module documentation
dreispt Oct 7, 2026
8a48843
[FIX] ai_tool: resolve tool model metadata with sudo
nobuQuartile Jul 21, 2026
02872a3
[FIX] ai_tool: _ai_get_date declares an invalid JSON Schema type
dreispt Oct 7, 2026
d9f01f1
[MIG] ai_oca_mcp: Migration to 19.0
dreispt Oct 7, 2026
65c9f37
[IMP] ai_oca_mcp: revamp module documentation
dreispt Oct 7, 2026
751606a
[IMP] ai_oca_mcp: expose server instructions in the initialize handshake
dreispt Oct 7, 2026
47c07e7
[ADD] ai_tool_read: generic read tools with per-field AI opt-out
nobuQuartile Oct 5, 2026
741aeee
[FIX] ai_tool: resolve tool model metadata with sudo
nobuQuartile Jul 21, 2026
9ff24c3
[FIX] ai_tool: _ai_get_date declares an invalid JSON Schema type
dreispt Oct 7, 2026
e0783b7
[FIX] ai_tool_read: review follow-ups
dreispt Oct 7, 2026
e472c86
[MIG] ai_tool_read: Migration to 19.0
dreispt Oct 7, 2026
bbfbdd9
[IMP] ai_tool_read: revamp documentation
dreispt Oct 7, 2026
7a85cbb
Merge remote-tracking branch 'dreispt/19.0-doc-ai_tool' into 19.0-tes…
dreispt Oct 8, 2026
cf242f4
Merge remote-tracking branch 'dreispt/19.0-mig-ai_oca_mcp' into 19.0-…
dreispt Oct 8, 2026
bcf1242
Merge remote-tracking branch 'dreispt/19.0-mig-ai_tool_read' into 19.…
dreispt Oct 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ Available addons
----------------
addon | version | maintainers | summary
--- | --- | --- | ---
[ai_oca_mcp](ai_oca_mcp/) | 19.0.1.0.0 | | MCP Interface for Odoo
[ai_tool](ai_tool/) | 19.0.1.0.0 | | We want to generate some specific AI Tools that might be used in other places, like MCP or native.

[//]: # (end addons)
Expand Down
194 changes: 194 additions & 0 deletions ai_oca_mcp/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
.. image:: https://odoo-community.org/readme-banner-image
:target: https://odoo-community.org/get-involved?utm_source=readme
:alt: Odoo Community Association

==========
Ai Oca Mcp
==========

..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:3d96a5eaf8e647e8ed470a84e39780908086610d1c72402ea1a85830b2d0e709
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fai-lightgray.png?logo=github
:target: https://github.com/OCA/ai/tree/19.0/ai_oca_mcp
:alt: OCA/ai
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/ai-19-0/ai-19-0-ai_oca_mcp
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
:target: https://runboat.odoo-community.org/builds?repo=OCA/ai&target_branch=19.0
:alt: Try me on Runboat

|badge1| |badge2| |badge3| |badge4| |badge5|

Expose Odoo as an MCP (Model Context Protocol) server, allowing external
AI clients — such as n8n, custom agents, or any MCP-compatible tool — to
invoke Odoo functions through the standardized MCP protocol.

Each MCP server publishes a set of **AI tools** (defined by the
``ai_tool`` module). Clients exchange JSON-RPC 2.0 messages with Odoo
over HTTP POST: ``tools/list`` returns the tool catalogue and
``tools/call`` executes a tool.

Key characteristics:

- **Authentication**: per-client API keys sent as Bearer tokens. Keys
are stored hashed and are shown only once, at creation.
- **Per-user permissions**: every key is bound to an Odoo user and calls
execute with that user's permissions — a client can only do what its
key's user can do.
- **Key lifecycle**: keys can carry an expiration date or be expired
manually at any time.
- **Audit log**: every tool invocation is recorded with its parameters
and result or error.

Notes:

- Transport is plain JSON-RPC over HTTP POST. SSE/streaming transports
and notifications are not supported.
- Claude Desktop requires OAuth 2.0 authorization. Odoo does not act as
an OAuth authorization server, so clients that mandate OAuth need an
extension (for example, validating externally-issued tokens via
``auth_jwt`` from OCA/server-auth).

**Table of contents**

.. contents::
:local:

Usage
=====

Configure an MCP server
-----------------------

1. Activate developer mode and go to
``Settings > Technical > AI > MCP Server``.
2. Create a server, give it a name, and select the ``generic`` tools you
want to expose.
3. The ``URL`` field shows the client endpoint — it already includes the
server's unique path key.
4. Optionally fill ``Instructions``: the ``initialize`` handshake always
returns an auto-generated capability summary (the server's purpose
and its tool list); this field appends extra guidance to it — domain
context, conventions, which tools to prefer. Per-tool ``description``
and input schemas remain the primary channel agents rely on, so write
them as if they were documentation for the model.

Create a client key
-------------------

1. Click **Add Key** on the server form.
2. Choose the user the client will act as and, optionally, an expiration
date.
3. Copy the generated key — it is stored hashed and **shown only once**.

Configure your client with:

- **URL**: the server's ``URL`` field
- **Authentication**: Bearer token
- **Token**: the generated API key

Connecting from n8n
~~~~~~~~~~~~~~~~~~~

Use the *MCP Client* node with:

- **Endpoint**: the server's ``URL`` field
- **Authentication**: Bearer Token
- **Token**: the generated API key
- **Transport**: HTTP — the endpoint expects POST requests; SSE is not
supported

Tool limitations
----------------

Only tools of kind ``generic`` are exposed. Tools requiring a record
context (``generic_model``, ``record``) cannot be called via MCP.

``ai_tool`` itself ships only demo tools (``get_date``,
``post_message``), of which just ``get_date`` is callable via MCP.
Generic read access to Odoo data — listing models, describing fields and
``search_read`` — is provided by the ``ai_tool_read`` addon, which also
adds a per-field AI opt-out for sensitive data. Other capabilities —
creating documents, triggering actions — come from extension modules
that define their own ``@aitool``-decorated methods and ``ai.tool``
records (see the ``ai_tool`` documentation). Whatever a tool can do is
limited by the permissions of the user bound to the API key.

Tools are not limited to reading data: a tool method runs real ORM code
and can also create or update records and call actions, always within
the key owner's permissions. Results must be JSON-serializable — return
plain dicts and convert dates, recordsets and binary values yourself —
otherwise the call is logged and returns a JSON-RPC error.

Security and auditing
---------------------

- Create one key per client, so a compromised client can be revoked
without affecting the others.
- Calls execute with the key owner's permissions: prefer a dedicated
user with minimal access rights to limit what clients can reach.
- Internal users can view their own keys and the related servers, but
only administrators can create or modify servers and keys.
- Keys can be expired manually from the server form, or automatically
once they reach their expiration date.
- ``AI > MCP Server Log`` records every tool invocation with its
parameters and result or error — use it to audit client activity.

Bug Tracker
===========

Bugs are tracked on `GitHub Issues <https://github.com/OCA/ai/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
`feedback <https://github.com/OCA/ai/issues/new?body=module:%20ai_oca_mcp%0Aversion:%2019.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.

Do not contact contributors directly about support or help with technical issues.

Credits
=======

Authors
-------

* Dixmit

Contributors
------------

- `Dixmit <https://www.dixmit.com>`__

- Enric Tobella

- Pierre Verkest pierre@verkest.fr
- Angel Moya amoyapardo@gmail.com
- Daniel Reis dreis.pt@gmail.com

Maintainers
-----------

This module is maintained by the OCA.

.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org

OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.

This module is part of the `OCA/ai <https://github.com/OCA/ai/tree/19.0/ai_oca_mcp>`_ project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
3 changes: 3 additions & 0 deletions ai_oca_mcp/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from . import controllers
from . import models
from . import wizards
23 changes: 23 additions & 0 deletions ai_oca_mcp/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Copyright 2026 Dixmit
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

{
"name": "Ai Oca Mcp",
"summary": """MCP Interface for Odoo""",
"version": "19.0.1.0.0",
"license": "AGPL-3",
"author": "Dixmit,Odoo Community Association (OCA)",
"website": "https://github.com/OCA/ai",
"depends": [
"ai_tool",
],
"data": [
"views/mcp_server_log.xml",
"security/ir.model.access.csv",
"security/security.xml",
"views/mcp_server_key.xml",
"wizards/mcp_server_key_add.xml",
"views/mcp_server.xml",
],
"demo": [],
}
1 change: 1 addition & 0 deletions ai_oca_mcp/controllers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import mcp_controller
78 changes: 78 additions & 0 deletions ai_oca_mcp/controllers/mcp_controller.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import json
import re

from odoo import fields, http
from odoo.http import request


class McpController(http.Controller):
@http.route(
"/mcp/<string:key>",
type="http",
auth="none",
methods=["POST"],
csrf=False,
readonly=False,
)
def mcp_endpoint(self, key, **kwargs):
match = re.match(
r"Bearer (.+)", request.httprequest.headers.get("Authorization", "")
)
payload = json.loads(request.httprequest.data.decode("utf-8"))
if not match:
return request.make_json_response(
{
"jsonrpc": "2.0",
"id": payload.get("id"),
"error": {"code": -32000, "message": "Connection failed"},
},
status=401,
)
security_key = match.group(1).strip()
server_id, expiration_date = (
request.env["mcp.server.key"]
.sudo()
._get_mcp_server_by_key(key, security_key)
)
if expiration_date and expiration_date < fields.Datetime.now():
request.env["mcp.server.key"].sudo().browse(server_id).expire_key()
server_id = False
if not server_id:
return request.make_json_response(
{
"jsonrpc": "2.0",
"id": payload.get("id"),
"error": {"code": -32000, "message": "Connection failed"},
}
)
mcp_server_key = request.env["mcp.server.key"].sudo().browse(server_id)
mcp_server_key = mcp_server_key.with_user(mcp_server_key.user_id.id)
method = payload.get("method")
if method == "initialize":
result = {
"protocolVersion": "2025-03-26",
"capabilities": {"tools": {}},
"serverInfo": {"name": "odoo-mcp", "version": "0.1.0"},
"instructions": mcp_server_key.server_id._get_instructions(),
}
return request.make_json_response(
{
"jsonrpc": "2.0",
"id": payload.get("id"),
"result": result,
}
)

if method == "tools/list":
return request.make_json_response(mcp_server_key._tools_list(payload))

if method == "tools/call":
return request.make_json_response(mcp_server_key._tools_call(payload))

return request.make_json_response(
{
"jsonrpc": "2.0",
"id": payload.get("id"),
"error": {"code": -32601, "message": "Method not found"},
}
)
Loading
Loading