odoosh-mcp-server 0.1.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. odoosh_mcp_server-0.1.4/.gitignore +11 -0
  2. odoosh_mcp_server-0.1.4/CHANGELOG.md +86 -0
  3. odoosh_mcp_server-0.1.4/LICENSE +21 -0
  4. odoosh_mcp_server-0.1.4/PKG-INFO +292 -0
  5. odoosh_mcp_server-0.1.4/README.md +248 -0
  6. odoosh_mcp_server-0.1.4/odoosh_mcp/__init__.py +10 -0
  7. odoosh_mcp_server-0.1.4/odoosh_mcp/audit.py +76 -0
  8. odoosh_mcp_server-0.1.4/odoosh_mcp/browser_session.py +164 -0
  9. odoosh_mcp_server-0.1.4/odoosh_mcp/cli.py +289 -0
  10. odoosh_mcp_server-0.1.4/odoosh_mcp/client.py +281 -0
  11. odoosh_mcp_server-0.1.4/odoosh_mcp/config.py +239 -0
  12. odoosh_mcp_server-0.1.4/odoosh_mcp/exceptions.py +68 -0
  13. odoosh_mcp_server-0.1.4/odoosh_mcp/operations/__init__.py +117 -0
  14. odoosh_mcp_server-0.1.4/odoosh_mcp/operations/_common.py +145 -0
  15. odoosh_mcp_server-0.1.4/odoosh_mcp/operations/_ssh_target.py +19 -0
  16. odoosh_mcp_server-0.1.4/odoosh_mcp/operations/audit_logs.py +25 -0
  17. odoosh_mcp_server-0.1.4/odoosh_mcp/operations/auth.py +123 -0
  18. odoosh_mcp_server-0.1.4/odoosh_mcp/operations/backups.py +188 -0
  19. odoosh_mcp_server-0.1.4/odoosh_mcp/operations/branches.py +257 -0
  20. odoosh_mcp_server-0.1.4/odoosh_mcp/operations/builds.py +103 -0
  21. odoosh_mcp_server-0.1.4/odoosh_mcp/operations/logs.py +157 -0
  22. odoosh_mcp_server-0.1.4/odoosh_mcp/operations/profiler.py +146 -0
  23. odoosh_mcp_server-0.1.4/odoosh_mcp/operations/profiles.py +84 -0
  24. odoosh_mcp_server-0.1.4/odoosh_mcp/operations/projects.py +153 -0
  25. odoosh_mcp_server-0.1.4/odoosh_mcp/operations/shell.py +133 -0
  26. odoosh_mcp_server-0.1.4/odoosh_mcp/operations/submodules.py +119 -0
  27. odoosh_mcp_server-0.1.4/odoosh_mcp/operations/users.py +181 -0
  28. odoosh_mcp_server-0.1.4/odoosh_mcp/server.py +173 -0
  29. odoosh_mcp_server-0.1.4/odoosh_mcp/ssh.py +61 -0
  30. odoosh_mcp_server-0.1.4/odoosh_mcp/validate.py +65 -0
  31. odoosh_mcp_server-0.1.4/odoosh_mcp/waits.py +32 -0
  32. odoosh_mcp_server-0.1.4/pyproject.toml +132 -0
  33. odoosh_mcp_server-0.1.4/scripts/render_tool_catalog.py +53 -0
  34. odoosh_mcp_server-0.1.4/tests/conftest.py +13 -0
  35. odoosh_mcp_server-0.1.4/tests/fixtures/audit_logs.json +26 -0
  36. odoosh_mcp_server-0.1.4/tests/fixtures/backups.json +10 -0
  37. odoosh_mcp_server-0.1.4/tests/fixtures/branches.json +62 -0
  38. odoosh_mcp_server-0.1.4/tests/fixtures/builds_per_branch.json +42 -0
  39. odoosh_mcp_server-0.1.4/tests/fixtures/flamegraph_list.json +11 -0
  40. odoosh_mcp_server-0.1.4/tests/fixtures/get_info.json +70 -0
  41. odoosh_mcp_server-0.1.4/tests/fixtures/get_settings.json +43 -0
  42. odoosh_mcp_server-0.1.4/tests/fixtures/logs_list.json +5 -0
  43. odoosh_mcp_server-0.1.4/tests/fixtures/notifications.json +9 -0
  44. odoosh_mcp_server-0.1.4/tests/fixtures/projects.json +41 -0
  45. odoosh_mcp_server-0.1.4/tests/fixtures/status.json +15 -0
  46. odoosh_mcp_server-0.1.4/tests/fixtures/users_list.json +8 -0
  47. odoosh_mcp_server-0.1.4/tests/integration/conftest.py +49 -0
  48. odoosh_mcp_server-0.1.4/tests/integration/test_live_auth.py +40 -0
  49. odoosh_mcp_server-0.1.4/tests/integration/test_live_read_only.py +33 -0
  50. odoosh_mcp_server-0.1.4/tests/integration/test_live_writes.py +96 -0
  51. odoosh_mcp_server-0.1.4/tests/test_audit.py +57 -0
  52. odoosh_mcp_server-0.1.4/tests/test_browser_session.py +266 -0
  53. odoosh_mcp_server-0.1.4/tests/test_cli.py +327 -0
  54. odoosh_mcp_server-0.1.4/tests/test_client.py +322 -0
  55. odoosh_mcp_server-0.1.4/tests/test_config.py +126 -0
  56. odoosh_mcp_server-0.1.4/tests/test_exceptions.py +20 -0
  57. odoosh_mcp_server-0.1.4/tests/test_operations__ssh_target.py +61 -0
  58. odoosh_mcp_server-0.1.4/tests/test_operations_audit_logs.py +36 -0
  59. odoosh_mcp_server-0.1.4/tests/test_operations_auth.py +145 -0
  60. odoosh_mcp_server-0.1.4/tests/test_operations_backups.py +214 -0
  61. odoosh_mcp_server-0.1.4/tests/test_operations_branches.py +173 -0
  62. odoosh_mcp_server-0.1.4/tests/test_operations_builds.py +83 -0
  63. odoosh_mcp_server-0.1.4/tests/test_operations_common.py +105 -0
  64. odoosh_mcp_server-0.1.4/tests/test_operations_logs.py +206 -0
  65. odoosh_mcp_server-0.1.4/tests/test_operations_profiler.py +202 -0
  66. odoosh_mcp_server-0.1.4/tests/test_operations_profiles.py +77 -0
  67. odoosh_mcp_server-0.1.4/tests/test_operations_projects.py +228 -0
  68. odoosh_mcp_server-0.1.4/tests/test_operations_shell.py +249 -0
  69. odoosh_mcp_server-0.1.4/tests/test_operations_submodules.py +156 -0
  70. odoosh_mcp_server-0.1.4/tests/test_operations_users.py +226 -0
  71. odoosh_mcp_server-0.1.4/tests/test_registry.py +97 -0
  72. odoosh_mcp_server-0.1.4/tests/test_registry_scope.py +123 -0
  73. odoosh_mcp_server-0.1.4/tests/test_server_handshake.py +19 -0
  74. odoosh_mcp_server-0.1.4/tests/test_server_permissions.py +145 -0
  75. odoosh_mcp_server-0.1.4/tests/test_ssh.py +55 -0
  76. odoosh_mcp_server-0.1.4/tests/test_validate.py +48 -0
  77. odoosh_mcp_server-0.1.4/tests/test_waits.py +17 -0
@@ -0,0 +1,11 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .pytest_cache/
4
+ .ruff_cache/
5
+ htmlcov/
6
+ coverage.xml
7
+ .coverage
8
+ *.egg-info/
9
+ dist/
10
+ build/
11
+ .venv/
@@ -0,0 +1,86 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here.
4
+ Format loosely follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
+
6
+ ## [Unreleased]
7
+
8
+ ## [0.1.4] - 2026-09-08
9
+
10
+ First release published to PyPI, and the first under the distribution name
11
+ **`odoosh-mcp-server`**.
12
+
13
+ ### Changed
14
+
15
+ - The published name is now `odoosh-mcp-server`. PyPI refused `odoosh-mcp` with
16
+ `400 The name 'odoosh-mcp' is too similar to an existing project`: it compares names with
17
+ every separator removed, and `odoo-sh-mcp` -- an unrelated tool that reads ORM metadata over
18
+ XML-RPC, published since April 2026 -- collapses to the same string. TestPyPI accepted all
19
+ four earlier uploads because it is a separate index with its own namespace, so no amount of
20
+ testing there could have surfaced this. The importable module (`odoosh_mcp`) and the command
21
+ (`odoosh-mcp`) are unchanged; only what follows `pip install` differs.
22
+
23
+ ## [0.1.3] - 2026-09-08
24
+
25
+ Published to TestPyPI only, as `odoosh-mcp`, and superseded by 0.1.4.
26
+
27
+ Every fix below was found by installing a TestPyPI build into an empty virtualenv and using it
28
+ against a live odoo.sh project, rather than by the test suite.
29
+
30
+ ### Fixed
31
+
32
+ - `auth login --from-browser` now finds the session in any browser profile, not only the
33
+ default one. `browser_cookie3` opens a single profile -- Chrome's `Default`, Firefox's
34
+ default-marked one -- and a browser with several profiles is ordinary. Observed live: Chrome's
35
+ `Default` store had last been written four months earlier and held nothing for odoo.sh while
36
+ `Profile 1` held the live session, so a user who was logged in was told to log in. Each
37
+ browser is now tried the library's way first, then once per profile store found on disk, most
38
+ recently written first.
39
+
40
+ ## [0.1.2] - 2026-09-08
41
+
42
+ Published to TestPyPI only, and superseded by 0.1.3.
43
+
44
+ ### Fixed
45
+
46
+ - The MCP handshake reported the SDK's version instead of this package's. FastMCP takes no
47
+ `version` and forwards none to the low-level server, so `serverInfo` answered `1.30.0` -- a
48
+ number that moved when the SDK was upgraded and never when this server changed. A client
49
+ asking which odoosh-mcp it is talking to now gets the package version.
50
+
51
+ ## [0.1.1] - 2026-09-08
52
+
53
+ Published to TestPyPI only, and superseded by 0.1.2.
54
+
55
+ ### Fixed
56
+
57
+ - `auth login --from-browser` no longer fails on a machine that has more than one browser
58
+ installed. `--browser auto` used `browser_cookie3.load()`, which walks every known browser and
59
+ aborts the whole call on the first unusable store, so an unreadable store hid a perfectly
60
+ readable one (observed live: `TypeError: expected str, bytes or os.PathLike object, not
61
+ NoneType`, while Chrome and Firefox were both readable on their own). Each supported store is
62
+ now tried in turn, and the error distinguishes "no store could be read" (paste `--session-id`)
63
+ from "a store was read and holds no odoo.sh cookie" (log in first).
64
+
65
+ ## [0.1.0] - 2026-09-06
66
+
67
+ First published release.
68
+
69
+ ### Added
70
+
71
+ - Repository scaffold, package layout, and v0.0 design spec
72
+ (`docs/superpowers/specs/2026-09-04-odoosh-mcp-v0-design.md`).
73
+ - Full v0.0 implementation: profile/auth management, HTTP client (control+worker planes), SSH
74
+ wrapper, 52 MCP tools covering projects, branches/stagings, builds, backups, collaborators,
75
+ submodules, code profiling, logs, and SSH-only operations (restart, raw logs, SQL), and a
76
+ `odoosh-mcp` CLI (`profile`, `auth`, `serve`, `run`) sharing the server's permission gate.
77
+ - Write confirmation via three verified strategies (audited, re-verified, SSH-evidenced) — see
78
+ docs/03-spikes.md §7.
79
+ - Opt-in integration test suite against a live odoo.sh project.
80
+ - Embedded authentication (v0.1): `auth login --from-browser` imports the odoo.sh `session_id`
81
+ cookie from Chrome or Firefox (optional `browser` extra), `auth status` reports session health,
82
+ age, provenance and whether odoo.sh's GitHub grant covers the repository routes, and
83
+ `authorize_github` turns odoo.sh's `InsufficientScopeError` into a one-command fix. Auth failures
84
+ now carry a machine-readable `remediation` object naming the action that resolves them.
85
+ - Authorization management from the CLI: `profile scope` and `profile permissions`, so the two
86
+ controls the safety model rests on are no longer hand-edited JSON.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Hugo Adan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,292 @@
1
+ Metadata-Version: 2.5
2
+ Name: odoosh-mcp-server
3
+ Version: 0.1.4
4
+ Summary: MCP server to administer odoo.sh projects agentically — stagings, backups, settings, builds, logs, SSH
5
+ Project-URL: Homepage, https://git.vauxoo.com/hugho-ad/odoo-sh-mcp
6
+ Project-URL: Repository, https://git.vauxoo.com/hugho-ad/odoo-sh-mcp
7
+ Project-URL: Documentation, https://git.vauxoo.com/hugho-ad/odoo-sh-mcp/-/blob/main/README.md
8
+ Project-URL: Issue Tracker, https://git.vauxoo.com/hugho-ad/odoo-sh-mcp/-/issues
9
+ Author-email: Hugo Adan <hugo@vauxoo.com>
10
+ Maintainer-email: Hugo Adan <hugo@vauxoo.com>
11
+ License: MIT
12
+ License-File: LICENSE
13
+ Keywords: agentic,ai,ai-tools,claude,claude-desktop,cursor,erp,json-rpc,llm,mcp,mcp-server,model-context-protocol,odoo,odoo-mcp,odoo-sh,odoosh,ssh,vscode
14
+ Classifier: Development Status :: 4 - Beta
15
+ Classifier: Environment :: Console
16
+ Classifier: Intended Audience :: Developers
17
+ Classifier: Intended Audience :: System Administrators
18
+ Classifier: License :: OSI Approved :: MIT License
19
+ Classifier: Operating System :: OS Independent
20
+ Classifier: Programming Language :: Python :: 3
21
+ Classifier: Programming Language :: Python :: 3.10
22
+ Classifier: Programming Language :: Python :: 3.11
23
+ Classifier: Programming Language :: Python :: 3.12
24
+ Classifier: Topic :: Internet
25
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
26
+ Classifier: Topic :: System :: Systems Administration
27
+ Requires-Python: >=3.10
28
+ Requires-Dist: click>=8.0.0
29
+ Requires-Dist: httpx>=0.27.0
30
+ Requires-Dist: mcp[cli]<2.0.0,>=1.0.0
31
+ Requires-Dist: pydantic>=2.0.0
32
+ Provides-Extra: browser
33
+ Requires-Dist: browser-cookie3>=0.19.1; extra == 'browser'
34
+ Provides-Extra: dev
35
+ Requires-Dist: build>=1.0.0; extra == 'dev'
36
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
37
+ Requires-Dist: pytest-cov>=4.1.0; extra == 'dev'
38
+ Requires-Dist: pytest-httpx>=0.30.0; extra == 'dev'
39
+ Requires-Dist: pytest>=7.0.0; extra == 'dev'
40
+ Requires-Dist: python-dotenv>=1.0.0; extra == 'dev'
41
+ Requires-Dist: ruff>=0.1.0; extra == 'dev'
42
+ Requires-Dist: twine>=5.0.0; extra == 'dev'
43
+ Description-Content-Type: text/markdown
44
+
45
+ # odoosh-mcp
46
+
47
+ MCP server to administer [odoo.sh](https://www.odoo.sh) projects agentically: create stagings,
48
+ download backups, manage settings/collaborators/submodules, run code profiling (flamegraphs),
49
+ read metadata (production DB size, worker count, staging slots), consume audit logs, manage
50
+ builds, tail logs, and reach the SSH-only operations (restart, raw logs, SQL) — all through one
51
+ permission-gated tool surface, inspired by the structure of
52
+ [odoo-mcp-multi](https://git.vauxoo.com/nhomar/mcp.odoo).
53
+
54
+ > **Status:** v0.0 in active development. See
55
+ > `docs/superpowers/specs/2026-09-04-odoosh-mcp-v0-design.md` for the full design and
56
+ > `docs/superpowers/plans/` for the task-by-task implementation plan.
57
+
58
+ ## Why this exists
59
+
60
+ The odoo.sh web UI has no public API. Every route this server calls was reverse-engineered from
61
+ the platform's own OWL frontend bundle and verified live against a disposable trial project. Full
62
+ methodology and the 58-route catalog live in `docs/00-discovery.md` and `docs/01-api-surface.md`.
63
+
64
+ Three facts shape the whole design (details in `docs/02-write-operations.md` and
65
+ `docs/03-spikes.md`):
66
+
67
+ 1. **Two HTTP planes, two credentials** — the control plane (`www.odoo.sh/app/*`, cookie auth)
68
+ and the worker plane (`<build.worker_url>/paas/*`, per-project access-token auth) do not share
69
+ credentials.
70
+ 2. **Writes lie about success, and there is no single way to confirm one.** A write can return
71
+ HTTP 200 with a `null` body while the change never applied — but live spikes proved the audit
72
+ log does not cover every route either (docs/03-spikes.md §7). This server uses three
73
+ confirmation strategies, matched per operation family: settings and branch-lifecycle writes
74
+ are confirmed against `audit_logs` growth; backup, submodule, profiler and collaborator writes
75
+ are re-verified by re-reading the affected resource's own listing (the project's `backups`,
76
+ `get_settings().submodules`, `flamegraph/list`, `get_settings().users`) — for submodules and
77
+ the profiler because live spikes proved there is no audit trace, for backups because the
78
+ listing is direct evidence the dump exists, and for collaborators because audit coverage was
79
+ never verified either way and an unverified assumption is not a confirmation; SSH-plane
80
+ operations (`restart_build`, `tail_log`, `search_log`, `run_sql`, `ssh_exec`) can only be
81
+ confirmed by the SSH command's own stdout and exit code, since `audit_logs` records nothing
82
+ more specific than "a shell was opened."
83
+
84
+ **A write that its strategy could not confirm is returned as `success: false`**, with the
85
+ evidence gathered so far under `data`. An unconfirmed write did not happen, whatever the HTTP
86
+ 200 said, so it is never reported as a success with a flag buried inside it.
87
+ 3. **Restart and raw log tailing have no HTTP route; SQL needs no special access route either.**
88
+ odoo.sh's own UI tells you to run `odoosh-restart` in the webshell for the former. For SQL,
89
+ `odoosh-sql-access` turned out to be for external BI-tool access on dedicated servers only
90
+ (docs/03-spikes.md §4) — the build's own shell already exports `PGDATABASE`/`PGUSER`/
91
+ `PGPASSWORD`/`PGHOST`, so `run_sql` simply runs `psql -c "<sql>"` over the same SSH session on
92
+ any project tier.
93
+
94
+ ## Safety model
95
+
96
+ - **`write_scope`** — each profile carries an allow-list of project names it is permitted to
97
+ write to (see `odoosh_mcp/config.py`). A write against a project outside that scope fails
98
+ before any HTTP request is made.
99
+ - **Risk tiers** — every tool is registered in `TOOL_REGISTRY` with one of `read`, `write_safe`
100
+ (reversible, no side effect outside the project), `write_external` (touches something outside
101
+ odoo.sh itself, e.g. GitHub, a subscription, another person's access), `destructive`
102
+ (irreversible), or `ssh_exec` (unrestricted shell access). See the tool catalog below for each
103
+ tool's tier.
104
+ - **`confirm=True`** — every `write_external`, `destructive`, and `ssh_exec` tool refuses to run
105
+ without an explicit `confirm=True` parameter; omitting it always fails safely with no side
106
+ effect. `run_sql` is the one exception worth calling out: it is registered `write_safe` (opening
107
+ a psql session on a build is a write-tier capability, and both paths are `write_scope`-gated),
108
+ it defaults to read-only by running psql with `PGOPTIONS=-c default_transaction_read_only=on`
109
+ so the *connection* is read-only, and it only requires `confirm=True` when called with
110
+ `read_only=False`. That guard is best-effort, not a sandbox — SQL that resets the GUC itself
111
+ escapes it, which is exactly why the tool is scope-gated as well.
112
+ - **Account-level tools take no `project`, so `write_scope` cannot restrain them.** `add_ssh_key`
113
+ is therefore `write_external` and requires `confirm=True`: the key it registers grants SSH on
114
+ the build of every project the account can reach, including projects deliberately left out of
115
+ the profile's `write_scope`, and the grant outlives the session.
116
+ - **Nothing here is anonymous.** Every action taken through this server is also audited on
117
+ odoo.sh itself under the human account that owns the configured `session_id` cookie — an agent
118
+ operating this MCP signs with that person's name on the platform's own audit log
119
+ (docs/03-spikes.md §6), whether or not that particular write happens to show up in
120
+ `get_audit_logs`.
121
+
122
+ `write_scope` and `permissions` are managed with `odoosh-mcp profile scope` and
123
+ `odoosh-mcp profile permissions`; run either with no flags to see the current value. Clearing a
124
+ scope grants writes on every project the session can reach, so it requires an explicit `--yes`.
125
+
126
+ ## Installation
127
+
128
+ ```bash
129
+ pip install odoosh-mcp-server
130
+ ```
131
+
132
+ The distribution is `odoosh-mcp-server`; the command it installs is `odoosh-mcp`, and the
133
+ importable module is `odoosh_mcp`. PyPI already hosts an unrelated `odoo-sh-mcp` -- a different
134
+ tool, which reads ORM metadata over XML-RPC rather than administering the platform -- and PyPI
135
+ treats the two names as the same once separators are stripped, so this one carries the suffix.
136
+
137
+ Importing the session cookie from a local browser needs one extra dependency, declared as the
138
+ optional `browser` extra:
139
+
140
+ ```bash
141
+ pip install "odoosh-mcp-server[browser]"
142
+ ```
143
+
144
+ Everything else works without it. To install an unreleased revision instead:
145
+
146
+ ```bash
147
+ pip install "git+ssh://git@git.vauxoo.com/hugho-ad/odoo-sh-mcp.git@main"
148
+ ```
149
+
150
+ ## Configuration
151
+
152
+ ```bash
153
+ odoosh-mcp profile add --name my-account --session-id <paste from the browser cookie>
154
+ odoosh-mcp auth login --profile my-account --from-browser # needs the browser extra
155
+ odoosh-mcp auth status --profile my-account
156
+ ```
157
+
158
+ odoo.sh issues no API token: signing in is GitHub OAuth against odoo.sh's own OAuth application,
159
+ and the credential it produces is a session cookie. The callback lands on odoo.sh rather than on a
160
+ port this server could listen on, so there is no way for the server to run the login itself. What
161
+ it can do is read that one cookie back from the browser that already holds it (`--from-browser`
162
+ reads only the `session_id` cookie, only for the odoo.sh host), and tell you when it has gone
163
+ stale.
164
+
165
+ `auth status` reports whether the session is alive, how old it is, where it came from, and whether
166
+ odoo.sh's GitHub grant covers the repository routes. It never prints the cookie — only a
167
+ six-character fingerprint, which is enough to confirm that a re-login actually replaced it.
168
+
169
+ Profiles are stored in `~/.config/odoosh-mcp/profiles.json` (directory mode 700, file mode 600).
170
+
171
+ ### When a tool answers with a remediation
172
+
173
+ An expired session and an insufficient GitHub grant both come back as an ordinary error envelope
174
+ carrying a `remediation` object that names the fix:
175
+
176
+ ```json
177
+ {"success": false, "error": "The token does not provide the required scope ...",
178
+ "route": "/app/branch/5118230/fork",
179
+ "remediation": {"action": "authorize_github",
180
+ "url": "https://github.com/login/oauth/authorize?...",
181
+ "scopes": ["read:user", "user:email", "repo"],
182
+ "hint": "call authorize_github, then retry"}}
183
+ ```
184
+
185
+ `create_staging`, `merge_branch` and `add_submodule` need GitHub scopes that a plain odoo.sh login
186
+ does not request, and the scope set differs per route, so the URL always comes from odoo.sh's own
187
+ error rather than from a constant in this package. Run:
188
+
189
+ ```bash
190
+ odoosh-mcp auth authorize-github --profile my-account
191
+ ```
192
+
193
+ It prints the scopes odoo.sh is asking GitHub for, opens the authorization page after you confirm,
194
+ and then polls until the grant lands. The MCP tool of the same name never opens a browser unless
195
+ it is called with `open_browser=true` — an agent gets the URL to hand to a human, not control of
196
+ somebody's desktop.
197
+
198
+ ## Usage
199
+
200
+ As an MCP server (stdio):
201
+
202
+ ```bash
203
+ odoosh-mcp serve
204
+ ```
205
+
206
+ From the command line directly:
207
+
208
+ ```bash
209
+ odoosh-mcp run get_project --param project=my-project
210
+ odoosh-mcp run list_branches --param project=my-project --json
211
+ ```
212
+
213
+ ## Tool catalog
214
+
215
+ Generated from `TOOL_REGISTRY` by `scripts/render_tool_catalog.py` — re-run that script and paste
216
+ its output here whenever a tool is added, renamed, or has its tier/confirm gate changed, so this
217
+ table cannot drift from what the server actually registers.
218
+
219
+ One side effect worth knowing before calling `download_backup` on a storage-constrained project:
220
+ triggering a dump also leaves a `manual` backup entry on odoo.sh (observed live 2026-09-05), which
221
+ counts against storage until it expires on its own — there is no route to delete it.
222
+
223
+ | Tool | Tier | Confirm | Description |
224
+ |---|---|---|---|
225
+ | `add_collaborator` | write_external | yes | Invite GitHub user `github_username` to the project at `access_level`. Requires confirm=True. |
226
+ | `add_ssh_key` | write_external | yes | Register `public_key` as an SSH key on the account. Requires confirm=True. |
227
+ | `add_submodule` | write_external | yes | Add submodule `submodule_url` (branch `submodule_branch`) at `path` on `branch_id`. Requires confirm=True. |
228
+ | `auth_status` | read | no | Report whether this profile can talk to odoo.sh right now, and with what authority. |
229
+ | `authorize_github` | read | no | Check odoo.sh's GitHub grant and return the URL that widens it when it is insufficient. |
230
+ | `check_auth` | always_allowed | no | Verify the profile's session_id cookie is still live against odoo.sh. |
231
+ | `clean_flamegraphs` | destructive | yes | Delete every captured flamegraph file for `build_id`. Requires confirm=True. |
232
+ | `create_backup` | write_safe | no | Trigger a manual backup of `branch`'s current build and wait for it to appear. |
233
+ | `create_staging` | write_external | yes | Fork `from_branch` into a new branch `name` at the given `stage`. Requires confirm=True. |
234
+ | `create_submodule_deploy_key` | write_safe | no | Create a deploy key for `submodule_url` so the project's repo can pull that private submodule. |
235
+ | `delete_branch` | destructive | yes | Permanently delete `branch_id` from the project. Requires confirm=True. |
236
+ | `delete_ssh_key` | destructive | yes | Remove SSH key `key_id` from the account. Requires confirm=True. |
237
+ | `delete_submodule_deploy_key` | destructive | yes | Delete submodule deploy key `submodule_id` (from `create_submodule_deploy_key`'s `key.id`). |
238
+ | `dismiss_notification` | write_safe | no | Dismiss notification `notification_id` on the project. |
239
+ | `download_backup` | write_safe | no | Trigger a downloadable dump of `branch`'s build and save it to `dest_dir`. |
240
+ | `download_flamegraph` | read | no | Download flamegraph `name` (from `list_flamegraphs`) for `build_id` into `dest_dir`. |
241
+ | `get_account_profile` | read | no | Fetch the odoo.sh account profile (name, SSH keys on file, etc.) for the signed-in user. |
242
+ | `get_audit_logs` | read | no | List the project's audit log, newest entries first, capped at `limit`. |
243
+ | `get_branch` | read | no | Fetch one branch's record by id, looked up from the full `list_branches` listing. |
244
+ | `get_branch_history` | read | no | Fetch a branch's build/commit history. |
245
+ | `get_branch_settings` | read | no | Fetch a branch's settings as odoo.sh reports them right now. |
246
+ | `get_build` | read | no | Fetch one build's record plus its install/runtime errors, by id. |
247
+ | `get_monitoring` | read | no | Uptime/status plus the URL of odoo.sh's own HTML monitoring page. |
248
+ | `get_project` | read | no | Fetch one project's identity plus storage/worker/staging-slot metadata. |
249
+ | `get_project_settings` | read | no | Fetch the project's full settings dict, including its `repository`, `submodules` and `users` sections. |
250
+ | `get_project_status` | read | no | Fetch the project's current status (the data odoo.sh's own status/monitoring page reads). |
251
+ | `import_github_ssh_keys` | write_safe | no | Import the account's GitHub-registered SSH public keys into odoo.sh. |
252
+ | `list_available_profiles` | always_allowed | no | List the profiles configured locally via `odoosh-mcp profile add` (names and settings only). |
253
+ | `list_backups` | read | no | List the project's backups (daily, remote, manual, update, restore, import, upgrade, other). |
254
+ | `list_branches` | read | no | List the project's branches, optionally filtered to one stage (production/staging/dev). |
255
+ | `list_builds` | read | no | List builds grouped per branch, optionally filtered to one branch, newest builds first. |
256
+ | `list_collaborators` | read | no | List the project's GitHub-linked collaborators, from `get_settings().users`. |
257
+ | `list_database_users` | read | no | List the Odoo database users on `branch_build_id`'s own database (worker-plane HTTP). |
258
+ | `list_flamegraphs` | read | no | List the flamegraph files already captured on `build_id` (worker-plane HTTP). |
259
+ | `list_logs` | read | no | List the log files available on `branch`'s current build (worker-plane HTTP, no SSH). |
260
+ | `list_notifications` | read | no | List the project's current notifications (e.g. "Database dump ready" download prompts). |
261
+ | `list_projects` | read | no | List every project (repo) visible to this account. |
262
+ | `list_submodules` | read | no | List the project's submodules and their deploy keys, from `get_settings().submodules`. |
263
+ | `merge_branch` | write_external | yes | Merge `source_branch_id` into `target_branch` (optionally rebasing). Requires confirm=True. |
264
+ | `rebuild_branch` | write_safe | no | Trigger a fresh build of `branch_id` from its current commit. |
265
+ | `restart_build` | write_external | yes | Restart `service` ("http" or "cron") on `branch`'s build via `odoosh-restart` over SSH. Requires confirm=True. |
266
+ | `restore_backup` | destructive | yes | Restore `target_branch_id` (production or staging only) to a prior backup. Requires confirm=True. |
267
+ | `revoke_collaborator` | destructive | yes | Revoke collaborator `user_access_id`'s access. Requires confirm=True. |
268
+ | `run_sql` | write_safe | no | Run arbitrary SQL on the build's Postgres via `psql -c` over SSH. |
269
+ | `search_log` | read | no | Grep `pattern` in log `name` over SSH, returning the last `lines` matches. |
270
+ | `set_branch_settings` | write_safe | no | Write one or more settings (e.g. `push_behavior`, `test_tags`, `modules`) on `branch`. |
271
+ | `set_branch_stage` | write_external | yes | Move a branch to a new stage (dev/staging/production). Requires confirm=True. |
272
+ | `set_collaborator_access` | write_external | yes | Change collaborator `user_access_id`'s access level to `access_level`. Requires confirm=True. |
273
+ | `set_project_settings` | write_external | yes | Write project-level settings (e.g. worker/storage limits, staging slot count). Requires confirm=True. |
274
+ | `ssh_exec` | ssh_exec | yes | Run an arbitrary shell `command` on `branch`'s build over SSH. Requires confirm=True. |
275
+ | `start_profiler` | write_safe | no | Start flamegraph profiling on `build_id`. |
276
+ | `stop_profiler` | write_safe | no | Stop flamegraph profiling on `build_id`, producing a downloadable flamegraph file. |
277
+ | `tail_log` | read | no | Tail the last `lines` of log `name` (e.g. "odoo", "install") over SSH. |
278
+ | `wait_for_build` | read | no | Block, polling, until `branch`'s current build reaches a terminal status. |
279
+
280
+ ## Development
281
+
282
+ ```bash
283
+ pyenv virtualenv 3.12 odoosh-mcp
284
+ pyenv activate odoosh-mcp
285
+ pip install -e ".[dev]"
286
+ pytest
287
+ ruff check odoosh_mcp/
288
+ ```
289
+
290
+ Integration tests that hit a live odoo.sh project are opt-in and read-only by default — see
291
+ `tests/integration/` and the design spec §10. `tests/integration/test_live_auth.py` checks the
292
+ authentication surface without writing anything or opening a browser.