odoosh-mcp
MCP server to administer odoo.sh projects agentically: create stagings, download backups, manage settings/collaborators/submodules, run code profiling (flamegraphs), read metadata (production DB size, worker count, staging slots), consume audit logs, manage builds, tail logs, and reach the SSH-only operations (restart, raw logs, SQL) — all through one permission-gated tool surface, inspired by the structure of odoo-mcp-multi.
Status: v0.0 in active development. See
docs/superpowers/specs/2026-09-04-odoosh-mcp-v0-design.mdfor the full design anddocs/superpowers/plans/for the task-by-task implementation plan.
Why this exists
The odoo.sh web UI has no public API. Every route this server calls was reverse-engineered from
the platform's own OWL frontend bundle and verified live against a disposable trial project. Full
methodology and the 58-route catalog live in docs/00-discovery.md and docs/01-api-surface.md.
Three facts shape the whole design (details in docs/02-write-operations.md and
docs/03-spikes.md):
-
Two HTTP planes, two credentials — the control plane (
www.odoo.sh/app/*, cookie auth) and the worker plane (<build.worker_url>/paas/*, per-project access-token auth) do not share credentials. -
Writes lie about success, and there is no single way to confirm one. A write can return HTTP 200 with a
nullbody while the change never applied — but live spikes proved the audit log does not cover every route either (docs/03-spikes.md §7). This server uses three confirmation strategies, matched per operation family: settings and branch-lifecycle writes are confirmed againstaudit_logsgrowth; backup, submodule, profiler and collaborator writes are re-verified by re-reading the affected resource's own listing (the project'sbackups,get_settings().submodules,flamegraph/list,get_settings().users) — for submodules and the profiler because live spikes proved there is no audit trace, for backups because the listing is direct evidence the dump exists, and for collaborators because audit coverage was never verified either way and an unverified assumption is not a confirmation; SSH-plane operations (restart_build,tail_log,search_log,run_sql,ssh_exec) can only be confirmed by the SSH command's own stdout and exit code, sinceaudit_logsrecords nothing more specific than "a shell was opened."A write that its strategy could not confirm is returned as
success: false, with the evidence gathered so far underdata. An unconfirmed write did not happen, whatever the HTTP 200 said, so it is never reported as a success with a flag buried inside it. -
Restart and raw log tailing have no HTTP route; SQL needs no special access route either. odoo.sh's own UI tells you to run
odoosh-restartin the webshell for the former. For SQL,odoosh-sql-accessturned out to be for external BI-tool access on dedicated servers only (docs/03-spikes.md §4) — the build's own shell already exportsPGDATABASE/PGUSER/PGPASSWORD/PGHOST, sorun_sqlsimply runspsql -c "<sql>"over the same SSH session on any project tier.
Safety model
write_scope— each profile carries an allow-list of project names it is permitted to write to (seeodoosh_mcp/config.py). A write against a project outside that scope fails before any HTTP request is made.- Risk tiers — every tool is registered in
TOOL_REGISTRYwith one ofread,write_safe(reversible, no side effect outside the project),write_external(touches something outside odoo.sh itself, e.g. GitHub, a subscription, another person's access),destructive(irreversible), orssh_exec(unrestricted shell access). See the tool catalog below for each tool's tier. confirm=True— everywrite_external,destructive, andssh_exectool refuses to run without an explicitconfirm=Trueparameter; omitting it always fails safely with no side effect.run_sqlis the one exception worth calling out: it is registeredwrite_safe(opening a psql session on a build is a write-tier capability, and both paths arewrite_scope-gated), it defaults to read-only by running psql withPGOPTIONS=-c default_transaction_read_only=onso the connection is read-only, and it only requiresconfirm=Truewhen called withread_only=False. That guard is best-effort, not a sandbox — SQL that resets the GUC itself escapes it, which is exactly why the tool is scope-gated as well.- Account-level tools take no
project, sowrite_scopecannot restrain them.add_ssh_keyis thereforewrite_externaland requiresconfirm=True: the key it registers grants SSH on the build of every project the account can reach, including projects deliberately left out of the profile'swrite_scope, and the grant outlives the session. - Nothing here is anonymous. Every action taken through this server is also audited on
odoo.sh itself under the human account that owns the configured
session_idcookie — an agent operating this MCP signs with that person's name on the platform's own audit log (docs/03-spikes.md §6), whether or not that particular write happens to show up inget_audit_logs.
write_scope and permissions are managed with odoosh-mcp profile scope and
odoosh-mcp profile permissions; run either with no flags to see the current value. Clearing a
scope grants writes on every project the session can reach, so it requires an explicit --yes.
Installation
pip install odoosh-mcp-server
The distribution is odoosh-mcp-server; it installs two console scripts, odoosh-mcp (the
canonical one, used throughout this README) and odoosh-mcp-server, which are the same CLI; the
importable module is odoosh_mcp. PyPI already hosts an unrelated odoo-sh-mcp -- a different
tool, which reads ORM metadata over XML-RPC rather than administering the platform -- and PyPI
treats the two names as the same once separators are stripped, so this one carries the suffix.
Importing the session cookie from a local browser needs one extra dependency, declared as the
optional browser extra:
pip install "odoosh-mcp-server[browser]"
Everything else works without it. To install an unreleased revision instead:
pip install "git+ssh://git@git.vauxoo.com/hugho-ad/odoosh-mcp-server.git@main"
Configuration
odoosh-mcp profile add --name my-account --session-id <paste from the browser cookie>
odoosh-mcp auth login --profile my-account --from-browser # needs the browser extra
odoosh-mcp auth status --profile my-account
odoo.sh issues no API token: signing in is GitHub OAuth against odoo.sh's own OAuth application,
and the credential it produces is a session cookie. The callback lands on odoo.sh rather than on a
port this server could listen on, so there is no way for the server to run the login itself. What
it can do is read that one cookie back from the browser that already holds it (--from-browser
reads only the session_id cookie, only for the odoo.sh host), and tell you when it has gone
stale.
auth status reports whether the session is alive, how old it is, where it came from, and whether
odoo.sh's GitHub grant covers the repository routes. It never prints the cookie — only a
six-character fingerprint, which is enough to confirm that a re-login actually replaced it.
Profiles are stored in ~/.config/odoosh-mcp/profiles.json (directory mode 700, file mode 600).
When a tool answers with a remediation
An expired session and an insufficient GitHub grant both come back as an ordinary error envelope
carrying a remediation object that names the fix:
{"success": false, "error": "The token does not provide the required scope ...",
"route": "/app/branch/5118230/fork",
"remediation": {"action": "authorize_github",
"url": "https://github.com/login/oauth/authorize?...",
"scopes": ["read:user", "user:email", "repo"],
"hint": "call authorize_github, then retry"}}
create_staging, merge_branch and add_submodule need GitHub scopes that a plain odoo.sh login
does not request, and the scope set differs per route, so the URL always comes from odoo.sh's own
error rather than from a constant in this package. Run:
odoosh-mcp auth authorize-github --profile my-account
It prints the scopes odoo.sh is asking GitHub for, opens the authorization page after you confirm,
and then polls until the grant lands. The MCP tool of the same name never opens a browser unless
it is called with open_browser=true — an agent gets the URL to hand to a human, not control of
somebody's desktop.
Usage
As an MCP server (stdio):
odoosh-mcp serve
From the command line directly:
odoosh-mcp run get_project --param project=my-project
odoosh-mcp run list_branches --param project=my-project --json
Tool catalog
Generated from TOOL_REGISTRY by scripts/render_tool_catalog.py — re-run that script and paste
its output here whenever a tool is added, renamed, or has its tier/confirm gate changed, so this
table cannot drift from what the server actually registers.
One side effect worth knowing before calling download_backup on a storage-constrained project:
triggering a dump also leaves a manual backup entry on odoo.sh (observed live 2026-09-05), which
counts against storage until it expires on its own — there is no route to delete it.
| Tool | Tier | Confirm | Description |
|---|---|---|---|
add_collaborator |
write_external | yes | Invite GitHub user github_username to the project at access_level. Requires confirm=True. |
add_ssh_key |
write_external | yes | Register public_key as an SSH key on the account. Requires confirm=True. |
add_submodule |
write_external | yes | Add submodule submodule_url (branch submodule_branch) at path on branch. Requires confirm=True. |
auth_status |
read | no | Report whether this profile can talk to odoo.sh right now, and with what authority. |
authorize_github |
read | no | Check odoo.sh's GitHub grant and return the URL that widens it when it is insufficient. |
check_auth |
always_allowed | no | Verify the profile's session_id cookie is still live against odoo.sh. |
clean_flamegraphs |
destructive | yes | Delete every captured flamegraph file for build_id. Requires confirm=True. |
create_backup |
write_safe | no | Trigger a manual backup of branch's current build and wait for it to appear. |
create_staging |
write_external | yes | Fork from_branch into a new branch name at the given stage. Requires confirm=True. |
create_submodule_deploy_key |
write_safe | no | Create a deploy key for submodule_url so the project's repo can pull that private submodule. |
delete_branch |
destructive | yes | Permanently delete branch from the project. Requires confirm=True. |
delete_ssh_key |
destructive | yes | Remove SSH key key_id from the account. Requires confirm=True. |
delete_submodule_deploy_key |
destructive | yes | Delete submodule deploy key submodule_id (from create_submodule_deploy_key's key.id). |
dismiss_notification |
write_safe | no | Dismiss notification notification_id on the project. |
download_backup |
write_safe | no | Trigger a downloadable dump of branch's build and save it to dest_dir. |
download_flamegraph |
read | no | Download flamegraph name (from list_flamegraphs) for build_id into dest_dir. |
get_account_profile |
read | no | Fetch the odoo.sh account profile (name, SSH keys on file, etc.) for the signed-in user. |
get_audit_logs |
read | no | List the project's audit log, newest entries first, capped at limit. |
get_branch |
read | no | Fetch one branch's record, resolved from the full list_branches listing. |
get_branch_history |
read | no | Fetch a branch's build/commit history. |
get_branch_settings |
read | no | Fetch a branch's settings as odoo.sh reports them right now. |
get_build |
read | no | Fetch one build's record plus its install/runtime errors, by id. |
get_monitoring |
read | no | Uptime/status plus the URL of odoo.sh's own HTML monitoring page. |
get_project |
read | no | Fetch one project's identity plus storage/worker/staging-slot metadata. |
get_project_settings |
read | no | Fetch the project's full settings dict, including its repository, submodules and users sections. |
get_project_status |
read | no | Fetch the project's current status (the data odoo.sh's own status/monitoring page reads). |
import_github_ssh_keys |
write_safe | no | Import the account's GitHub-registered SSH public keys into odoo.sh. |
list_available_profiles |
always_allowed | no | List the profiles configured locally via odoosh-mcp profile add (names and settings only). |
list_backups |
read | no | List the project's backups (daily, remote, manual, update, restore, import, upgrade, other). |
list_branches |
read | no | List the project's branches, optionally filtered to one stage (production/staging/dev). |
list_builds |
read | no | List builds grouped per branch, optionally filtered to one branch, newest builds first. |
list_collaborators |
read | no | List the project's GitHub-linked collaborators, from get_settings().users. |
list_database_users |
read | no | List the Odoo database users on branch_build_id's own database (worker-plane HTTP). |
list_flamegraphs |
read | no | List the flamegraph files already captured on build_id (worker-plane HTTP). |
list_logs |
read | no | List the log files available on branch's current build (worker-plane HTTP, no SSH). |
list_notifications |
read | no | List the project's current notifications (e.g. "Database dump ready" download prompts). |
list_projects |
read | no | List every project (repo) visible to this account. |
list_submodules |
read | no | List the project's submodules and their deploy keys, from get_settings().submodules. |
merge_branch |
write_external | yes | Merge source_branch into target_branch (optionally rebasing). Requires confirm=True. |
rebuild_branch |
write_safe | no | Trigger a fresh build of branch from its current commit. |
restart_build |
write_external | yes | Restart service ("http" or "cron") on branch's build via odoosh-restart over SSH. Requires confirm=True. |
restore_backup |
destructive | yes | Restore target_branch_id (production or staging only) to a prior backup. Requires confirm=True. |
revoke_collaborator |
destructive | yes | Revoke collaborator user_access_id's access. Requires confirm=True. |
run_sql |
write_safe | no | Run arbitrary SQL on the build's Postgres via psql -c over SSH. |
search_log |
read | no | Grep pattern in log name over SSH, returning the last lines matches. |
set_branch_settings |
write_safe | no | Write one or more settings (e.g. push_behavior, test_tags, modules) on branch. |
set_branch_stage |
write_external | yes | Move a branch to a new stage (dev/staging/production). Requires confirm=True. |
set_collaborator_access |
write_external | yes | Change collaborator user_access_id's access level to access_level. Requires confirm=True. |
set_project_settings |
write_external | yes | Write project-level settings (e.g. worker/storage limits, staging slot count). Requires confirm=True. |
ssh_exec |
ssh_exec | yes | Run an arbitrary shell command on branch's build over SSH. Requires confirm=True. |
start_profiler |
write_safe | no | Start flamegraph profiling on build_id. |
stop_profiler |
write_safe | no | Stop flamegraph profiling on build_id, producing a downloadable flamegraph file. |
tail_log |
read | no | Tail the last lines of log name (e.g. "odoo", "install") over SSH. |
wait_for_build |
read | no | Block, polling, until branch's current build reaches a terminal status. |
Development
pyenv virtualenv 3.12 odoosh-mcp
pyenv activate odoosh-mcp
pip install -e ".[dev]"
pytest
ruff check odoosh_mcp/
Integration tests that hit a live odoo.sh project are opt-in and read-only by default — see
tests/integration/ and the design spec §10. tests/integration/test_live_auth.py checks the
authentication surface without writing anything or opening a browser.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file odoosh_mcp_server-0.2.0.tar.gz.
File metadata
- Download URL: odoosh_mcp_server-0.2.0.tar.gz
- Upload date:
- Size: 97.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a3d22f09b084bd6e29c6e5bf495c9cef28cb7165de31ef52cc430194c42dea4e
|
|
| MD5 |
4a40ce46e9e7e7d1c59cf6e872360ccd
|
|
| BLAKE2b-256 |
2509283239a56a7cbfc74bcf14554bb61e4fb02f6146a6d53e2c0e44cd8054cf
|
File details
Details for the file odoosh_mcp_server-0.2.0-py3-none-any.whl.
File metadata
- Download URL: odoosh_mcp_server-0.2.0-py3-none-any.whl
- Upload date:
- Size: 72.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
66e69f035840061a3966b37a9d886eeec31c9288c5491260a469083188d179dd
|
|
| MD5 |
a59901133d9d5d521eb65d662a2e3a62
|
|
| BLAKE2b-256 |
6ca55f9b2916e7bf3e5f39a6f6401723c2b87eb2352a984f10e7c6558da4d979
|