Security
What MCP Server for Odoo can read and write, what it stores, and what leaves your instance.
The model
A token is bound to one Odoo user. Every request it authenticates runs through env[model].with_user(that person) - the same scope and tool layer the Odoo screens use. There is no query path in this module.
An MCP endpoint that read as an administrator would hand every connected client the keys to the business, and no prompt on the client side changes what a server is willing to return.
The route
/mcp/v1 is auth="none", which means every request begins as nobody. The bearer token is the only thing that narrows it to a user, and su=False is passed explicitly when it does.
The route is declared readonly=False. Odoo 18 defaults auth="none" routes to a read-only cursor, and this endpoint writes on every request - the token's usage counter and the audit row. On a read-only cursor Odoo retries on a read/write one, so the endpoint appears to work while the audit insert is silently lost. test_the_endpoint_is_not_read_only exists because nothing observable from outside would tell you.
Tokens
- Stored as a SHA-256 digest plus a short non-secret prefix used to find the row. Compared in constant time.
- Shown once, at issue. There is no way to recover one - which is what makes a stolen database backup much less bad than a stolen token.
- Owner and digest are frozen after issue, so revocation cannot be downgraded to a rename.
- Expiry and revocation both take effect on the next request.
- A token for an archived user stops working.
Tool resolution
A tool name arrives from outside - from a language model, a replayed conversation, or a client that composed the request itself - so it is re-resolved against that person's scope on every call, never trusted from a previous tools/list.
Error handling
- A malformed or out-of-scope tool call comes back as an MCP tool result with isError, not a protocol error: the model is meant to read it and correct itself, and a protocol error would end the session instead.
- Internal failures return an unspecific message. The traceback goes to the server log, where the administrator can read it and the client cannot.
- Request bodies above 256 KB are refused unread.
Transport
Serve Odoo over HTTPS. A bearer token over plain HTTP is readable by anything in between.
Reporting a vulnerability
info@technovision.dev, with the Odoo version, the module version and enough detail to reproduce.