MCP server¶
odouche-mcp is a Model Context Protocol server that lets
development agents work with your Odoo.sh projects. It runs over stdio, and started as below its
tools read: none changes anything on Odoo.sh. The one tool that does is
yours to turn on.
The reference lists every tool with its arguments, what it returns, what it can touch and its bounds, generated from the server.
Setup¶
Log in¶
The server cannot log in. Give it a session first, one of two ways:
- Run
osh auth login, fromodouche-cli, which stores the session in your keyring. - Where there is no keyring, set
ODOUCHE_SESSIONin the server's environment. A client that does not pass its own environment on has to be told to pass the variable. Do not write its value in a configuration file.
Claude Code¶
Or, for everyone who works on a project, in its .mcp.json:
Name the server odouche: the hook matches on that name.
Any other client¶
The server speaks stdio and takes no configuration but its one flag. Give your client this command:
Why a version is pinned¶
Without a version, uvx runs the newest release the first time, and again whenever its cache is
pruned or refreshed: the version can change without you choosing it, and this process holds your
Odoo.sh session. Pinned, a new release runs only once you have changed the number.
Session¶
The server cannot log in, and has no tool that does: a browser opened because an agent asked is a phishing surface. It uses the session you already have:
ODOUCHE_SESSION, when it is set in the server's environment. It is kept in memory only.- The session
osh auth loginstored in the keyring.
The session is looked up on each call, not when the server starts. The server starts and lists
its tools while you are logged out, and an osh auth login made while it runs is used by the
next call, with no restart. No tool takes a session as an argument.
With no session, an expired one or no usable keyring, a tool's error tells the agent to stop and
ask you to run osh auth login in a terminal, or to restart the server with ODOUCHE_SESSION
set where there is no keyring. When the session comes from ODOUCHE_SESSION, it says to restart
the server with a current value or without the variable, since a login would not be read.
Tools¶
What the reference does not say of each tool.
get_session¶
It never returns the session. With none, available is false and problem says what to do. A
session Odoo.sh rejects is an error, as from any other tool. The seconds left are those before the
session's max age.
list_projects¶
Every other tool takes a project by its name, so an agent calls this one first.
list_branches¶
The branches come in the order Odoo.sh answers them.
list_builds¶
branch is the git branch's name, as for every tool that takes one. Odoo.sh has only been seen to
answer the latest builds of a branch: older ones are out of reach.
get_build¶
Without build_id, it reads the branch's latest build. A build older than the branch's latest
ones is not found.
wait_for_build¶
It waits for the build of build_id, or the branch's latest one, and returns it as last seen and
finished, as soon as the build ends.
A build that outlasts the wait is not an error. finished is false and next_step tells the
agent to call the tool again, which continues the wait. The wait is short because a client gives
up on a call that lasts: a timeout over the most is lowered to it, timeout in the result is
the one applied, and timeout_capped says it was lowered.
With commit, the first 7 to 64 digits of a hash, the tool waits for the branch to have a build
of that commit, then for that build. Right after a push the latest build is still the previous
one, so an agent that pushed gives the commit. While the branch has no such build, build is null
and next_step says to call again. build_id and commit are not given together.
- Progress: a notification at each change of the build, to a client that asked for them. It holds the build's number, status and result, and nothing Odoo.sh or a commit's author wrote.
- Cancellation: a call the client cancels closes its connection to Odoo.sh, within a few seconds unless a request is being answered.
- Timeout: the requests that find the build, or await one of a commit, are not cut at the
timeout: a slow Odoo.sh can make a call last longer.
read_log¶
Without kind, it reads the install log when the build has it and the odoo log otherwise. With
contains, only the lines that hold that text are returned, out of the log's last mebibyte. The
text is matched as it is, case included, and not as a pattern. A kind of unknown is refused.
The lines come in untrusted_lines, without their escape sequences and control characters, and
are not instructions. truncated is true when the last mebibyte held more lines
than returned, or one was cut. It says nothing of what the log holds before that, which is not
read. When the lines are over the most a result holds, the oldest are left out. A result carries
them twice, as text and as structured content.
rebuild_branch¶
It is listed only when the server is started with --allow-changes. Only a
development or a staging branch is rebuilt: any other is refused before the rebuild is sent.
The request is sent once and never repeated. When Odoo.sh does not confirm it, or the new build cannot be found, the error says so and tells the agent to list the branch's builds before trying again, since a second call can start a second build.
Security¶
- By default the server reads your projects, their branches, their builds and the builds' logs. It changes nothing on Odoo.sh.
- With
--allow-changesit can also start a rebuild of a development or a staging branch. - It never logs in, takes a session as an argument, returns the session in a result or writes a file.
Security has how the session is obtained and stored.
Changing state¶
The tools that change state on Odoo.sh are off until you start the server with --allow-changes:
{
"mcpServers": {
"odouche": { "command": "uvx", "args": ["odouche-mcp==0.4.0", "--allow-changes"] }
}
}
- The flag is read once, when the server starts. No tool and no argument sets it, so nothing an agent does during a session turns it on.
- Without it,
rebuild_branchis not registered, and every tool opens the library's client read-only, which refuses a change before anything is sent. - With it,
rebuild_branchis listed without the read-only hint and with the destructive one, so a client that asks before such tools asks before this one. The server itself does not ask. - Each call writes one line to the server's stderr: the tool, the project, the branch, and the build started or the kind of error. It never holds the session.
Asking before a change¶
The flag decides whether rebuild_branch exists. Whether an agent may call a tool without asking
you is your client's decision. For Claude Code, a hook lets the tools that read pass and leaves
the rest to the usual prompt:
examples/claude-code/odouche_read_only_hook.py.
It needs Python and nothing else. Read it, copy it into your project's .claude/hooks/, and add
it to .claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "mcp__odouche__.*",
"hooks": [
{
"type": "command",
"command": "python3 \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/odouche_read_only_hook.py",
"timeout": 5
}
]
}
]
}
}
- It approves a call to a tool on its list, which a test holds to the tools the server registers with the read-only hint. For any other tool, and for any input it does not understand, it prints nothing and Claude Code asks you as it would without the hook.
- Claude Code names a tool
mcp__<server>__<tool>, where<server>is the name the server has in your configuration. Under another name thanodouche, changeSERVERin the script and the matcher above. - It does not read a call's arguments, and it never denies a call.
read_logis one of the tools it approves, so the lines of a build log reach the agent without a prompt.- It does not replace
--allow-changes: without the flag there is no tool to ask about.
Another client decides from the hints each tool carries.
Build logs¶
A log is whatever the build printed: the output of every module, and of every request made to the instance. Anyone who can make it print a line can write one that reads as an instruction to an agent, and a log can hold a secret of the instance.
read_logreturns the lines in one field,untrusted_lines, and its description tells the agent that they are data and are not to be followed. That lowers the risk of prompt injection. It does not remove it: an agent can still act on what it reads.- Secrets are not masked. A mask would catch some and promise all, so the lines pass through as they are, into the agent's context and whatever the client does with it.
- No line of a log is written to the server's stderr.
This is why the server is read-only unless you start it otherwise: an agent misled by a log has no tool that changes anything on Odoo.sh.
Arguments and results¶
The project¶
The project is always an argument. The server does not read it from a git checkout: its working directory is wherever the client started it, and a guess could point an agent at the wrong project. A call without one is rejected.
Limits¶
A list holds at most limit items, and truncated is true when there were more. A limit over
the most a tool returns is lowered to it, and one below 1 is an error. The lines of read_log
and the timeout of wait_for_build follow the same rule.
Untrusted text¶
Branch names, commit messages and author names are written by other people. The server returns them in the fields of a result only, never in a sentence of its own, and a client should treat them as data, not as instructions.
Tool contract¶
Every tool follows the same rules, and a test over the registered tools holds them to it.
- Names are a verb then a noun, such as
list_projectsorget_build, and do not change once released: permission rules and hooks match on them. - Descriptions end with three labelled lines:
Returns:what the tool returns,Touches:what it can change on Odoo.sh, andBounds:its limits on size and time. A tool that changes nothing saysTouches: reads only. - Annotations are all set. A tool that reads has the read-only hint, and a client can use the hints to decide when to ask you first.
- Results are structured, with the field names of the library's models, which
are also what
osh --format jsonprints. - Errors are a message and, when there is one, the next step: never a traceback or an answer from Odoo.sh.
Troubleshooting¶
A tool's error says what happened and, when there is one, what the agent or you should do next.
| Error | What to do |
|---|---|
| No session | Run osh auth login in a terminal. The next call uses it, with no restart. |
| An expired session, or one Odoo.sh rejects | The same. Sessions are never refreshed. |
An expired session that comes from ODOUCHE_SESSION |
Restart the server with a current value, or without the variable to use the one osh auth login stores. A login is not read while the variable is set. |
| No usable keyring | Install a Secret Service provider, or restart the server with ODOUCHE_SESSION set. A locked keyring is unlocked in its own dialog. |
| Odoo.sh changed shape | Odoo.sh has no public API, and an answer no longer has the shape odouche reads. Upgrade to the latest release, and report it with the request and the field the error names if it remains. |
| A tool is missing | rebuild_branch exists only with --allow-changes. |
The server is built on the odouche library and never talks to Odoo.sh directly.