You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Working checklist for ADR 0004. The ADR is the decision, this is the ticking.
Order is a gate, not a preference: phase 1 unblocks 2 and 3, and phase 3 only starts if the last box of phase 0 comes back yes.
Status 2026-09-05: all three phases are on main and released as 3.1.0 (e7838fe, #130). What is left needs a hand outside this session: the tag, the admin pin, and the one real-customer test that decides whether the app widens.
Left to do, in order
Tag v3.1.0 on e7838fe. The remote session cannot push tags (403 from the proxy); from any checkout: git fetch origin main && git tag -a v3.1.0 e7838fe4d1b760e118e48e7ffdfee1eb797f1a80 -m "v3.1.0 -- interactive elements" && git push origin v3.1.0. (3.0.0 was released at f26966a but never tagged; 3.1.0 carries everything, so one tag is enough.)
odoo-mcp-pro-admin: bump the git pin to v3.1.0, confirm the create_fastmcp_app call site and the streamable_http_app(**STREAMABLE_HTTP_OPTIONS) call, staging before prod. Done 2026-09-05: pantalytics/odoo-mcp-pro-admin#217, merged as 8035839, live on staging and prod. create_fastmcp_app survived unchanged; stateless_http/json_response moved onto streamable_http_app() together with host and transport_security (app.settings no longer carries those three, log_level is still there). FastMCP -> MCPServer in two modules and one test. The admin registers no tool overrides, so OdooToolHandler.tool() needed nothing. Verified in the deployed prod container: 23 tools, register_payment among them, public SERVER_VERSION 3.1.0
Smoke stdio against a live Odoo 19 (no Odoo in the session that did the migration)
Verify in Claude on Odoo 19: backorder confirm and register payment through the 2026-07-28 form, both round trips, and check which protocol version claude.ai negotiates today
Try the register-payment app in Claude with one real customer on Odoo 19
Decide from that: widen to the other four wizards, or stop and stay on MRTR
Phase 0 -- answer before committing (half a day)
Answered 2026-09-05 from the installed mcp 2.1.1, not from the docs.
Where do server instructions live now that the initialize handshake is gone? Still a constructor argument of MCPServer; delivered via server/discover. Verified the client receives them.
What replaces auth= and token_verifier= on the new server class? Nothing: same names, same position on MCPServer. The admin call site should survive unchanged.
Are stateless_http and json_response still parameters, or the default now that sessions are gone? Still parameters, but of streamable_http_app() / run_streamable_http_async(), both defaulting to False. Hence STREAMABLE_HTTP_OPTIONS.
Gate for phase 3: can a Python server declare _meta.ui.resourceUri on a tool and serve a ui:// resource at all? Yes. mcp.server.apps.Apps is in the SDK: @apps.tool(resource_uri="ui://..."), add_html_resource(uri, html, csp=..., permissions=...), passed as MCPServer(extensions=[apps]). Also client_supports_apps(ctx) for the text fallback SEP-2133 requires.
Found on the way, not in the list: on 2.x only a ToolError / ResourceError carries its message to the model. Handled in phase 1 (as_tool_error / as_resource_error).
Bump the pin: mcp>=1.27.2,<2 -> mcp>=2.1.1,<3. mcp-types comes along as an exact-pinned dependency, no separate install
server.py: FastMCP -> MCPServer. create_fastmcp_app keeps its exported name
Same import swap in tools/handler.py, resources/handler.py, skills.py
mcp.types -> no change needed: it survives in 2.x as a mirror of mcp_types
McpError -> MCPError: we never imported the SDK's; our own error_handling.MCPError is unrelated and untouched. ADR 0003's contract is unaffected
snake_case sweep: 68 ToolAnnotations kwargs in 8 files. No model_dump of protocol models in our code
TransportSecuritySettings still at mcp.server.transport_security, now passed to streamable_http_app() instead of set on settings
9 test files importing mcp.server.fastmcp, plus test_server_foundation (settings object gone) and test_tools_crud (registered functions raise ToolError with ours as __cause__)
Full suite green: 579 passed, same as baseline. ruff, check_max_lines.py clean. ty from 23 to 2 pre-existing warnings
Smoke both transports: in-process and over streamable-http against run_http, with the real 2.x client, protocol 2026-07-28 negotiated, error text identical to 1.x
PR #128 (merged). Design note from phase 0 held up and is pinned by a test: the SDK rejects an InputRequiredResult as an invalid tools/call result for a pre-2026-07-28 client, so the two-step decision / followup is not only the deprecation-window fallback, it is the only thing an older client can use. Gated on ctx.protocol_versionand the client's form-elicitation capability.
tools/methods.py: return the SDK's InputRequiredResult instead of the hand-rolled followup dict, when the client can answer it (tools/input_required.py)
Keep accepting the current decision shape, and the followup dict for older clients; a test per shape (tests/test_execute_method_input_required.py, four in-process round trips over the real 2.x client)
tools/wizards.py schemas unchanged: the same primitive-only pydantic models render straight into the elicitation schema (render_elicitation_schema)
Declining the form: result_kind: declined, nothing touched, the method is not even re-run
Not asked as a form on purpose: a chained second wizard. The retry re-runs the method and gets the first wizard back, so the answer would land on the wrong one. Stays the dict.
Phase 3 -- exactly one MCP App
PR #129 (merged). Read-side apps (search results as tables, dashboards) are dropped on purpose.
ui://odoo/register-payment.html via Apps.add_html_resource, one document, served as text/html;profile=mcp-app; the wheel carries it
The tool: register_payment (= execute_method fixed to account.move.action_register_payment), bound through _meta.ui.resourceUri. An Apps client gets the followup dict the form reads; a client without Apps gets execute_method's behaviour, dict or 2026-07-28 form (client_supports_apps)
CSP: every asset inline, no external origin (pinned by a test), so the host's default CSP is enough
The form completes the wizard by calling register_payment back from inside the iframe, then ui/update-model-context so the model learns the outcome. Verified in headless Chromium against a fake host, including the one thing the docs do not say: a sandbox without allow-forms blocks native form submission, so the submit is a click handler on a type=button
Working checklist for ADR 0004. The ADR is the decision, this is the ticking.
Order is a gate, not a preference: phase 1 unblocks 2 and 3, and phase 3 only starts if the last box of phase 0 comes back yes.
Status 2026-09-05: all three phases are on
mainand released as 3.1.0 (e7838fe, #130). What is left needs a hand outside this session: the tag, the admin pin, and the one real-customer test that decides whether the app widens.Left to do, in order
v3.1.0one7838fe. The remote session cannot push tags (403 from the proxy); from any checkout:git fetch origin main && git tag -a v3.1.0 e7838fe4d1b760e118e48e7ffdfee1eb797f1a80 -m "v3.1.0 -- interactive elements" && git push origin v3.1.0. (3.0.0 was released atf26966abut never tagged; 3.1.0 carries everything, so one tag is enough.)v3.1.0, confirm thecreate_fastmcp_appcall site and thestreamable_http_app(**STREAMABLE_HTTP_OPTIONS)call, staging before prod. Done 2026-09-05: pantalytics/odoo-mcp-pro-admin#217, merged as8035839, live on staging and prod.create_fastmcp_appsurvived unchanged;stateless_http/json_responsemoved ontostreamable_http_app()together withhostandtransport_security(app.settingsno longer carries those three,log_levelis still there).FastMCP->MCPServerin two modules and one test. The admin registers no tool overrides, soOdooToolHandler.tool()needed nothing. Verified in the deployed prod container: 23 tools,register_paymentamong them, publicSERVER_VERSION3.1.0Phase 0 -- answer before committing (half a day)
Answered 2026-09-05 from the installed
mcp2.1.1, not from the docs.instructionslive now that the initialize handshake is gone? Still a constructor argument ofMCPServer; delivered viaserver/discover. Verified the client receives them.auth=andtoken_verifier=on the new server class? Nothing: same names, same position onMCPServer. The admin call site should survive unchanged.stateless_httpandjson_responsestill parameters, or the default now that sessions are gone? Still parameters, but ofstreamable_http_app()/run_streamable_http_async(), both defaulting toFalse. HenceSTREAMABLE_HTTP_OPTIONS._meta.ui.resourceUrion a tool and serve aui://resource at all? Yes.mcp.server.apps.Appsis in the SDK:@apps.tool(resource_uri="ui://..."),add_html_resource(uri, html, csp=..., permissions=...), passed asMCPServer(extensions=[apps]). Alsoclient_supports_apps(ctx)for the text fallback SEP-2133 requires.Found on the way, not in the list: on 2.x only a
ToolError/ResourceErrorcarries its message to the model. Handled in phase 1 (as_tool_error/as_resource_error).Phase 1 -- the SDK v2 migration
PR #125 (merged), release #127 (merged,
3.0.0).mcp>=1.27.2,<2->mcp>=2.1.1,<3.mcp-typescomes along as an exact-pinned dependency, no separate installserver.py:FastMCP->MCPServer.create_fastmcp_appkeeps its exported nametools/handler.py,resources/handler.py,skills.pymcp.types-> no change needed: it survives in 2.x as a mirror ofmcp_typesMcpError->MCPError: we never imported the SDK's; our ownerror_handling.MCPErroris unrelated and untouched. ADR 0003's contract is unaffectedToolAnnotationskwargs in 8 files. Nomodel_dumpof protocol models in our codeTransportSecuritySettingsstill atmcp.server.transport_security, now passed tostreamable_http_app()instead of set on settingsmcp.server.fastmcp, plustest_server_foundation(settings object gone) andtest_tools_crud(registered functions raiseToolErrorwith ours as__cause__)ruff,check_max_lines.pyclean.tyfrom 23 to 2 pre-existing warningsrun_http, with the real 2.x client, protocol2026-07-28negotiated, error text identical to 1.x3.0.0merged (release: 3.0.0 #127,f26966a)Phase 2 -- MRTR
PR #128 (merged). Design note from phase 0 held up and is pinned by a test: the SDK rejects an
InputRequiredResultas an invalidtools/callresult for a pre-2026-07-28 client, so the two-stepdecision/followupis not only the deprecation-window fallback, it is the only thing an older client can use. Gated onctx.protocol_versionand the client's form-elicitation capability.tools/methods.py: return the SDK'sInputRequiredResultinstead of the hand-rolledfollowupdict, when the client can answer it (tools/input_required.py)decisionshape, and thefollowupdict for older clients; a test per shape (tests/test_execute_method_input_required.py, four in-process round trips over the real 2.x client)tools/wizards.pyschemas unchanged: the same primitive-only pydantic models render straight into the elicitation schema (render_elicitation_schema)result_kind: declined, nothing touched, the method is not even re-runPhase 3 -- exactly one MCP App
PR #129 (merged). Read-side apps (search results as tables, dashboards) are dropped on purpose.
ui://odoo/register-payment.htmlviaApps.add_html_resource, one document, served astext/html;profile=mcp-app; the wheel carries itregister_payment(=execute_methodfixed toaccount.move.action_register_payment), bound through_meta.ui.resourceUri. An Apps client gets thefollowupdict the form reads; a client without Apps getsexecute_method's behaviour, dict or 2026-07-28 form (client_supports_apps)register_paymentback from inside the iframe, thenui/update-model-contextso the model learns the outcome. Verified in headless Chromium against a fake host, including the one thing the docs do not say: a sandbox withoutallow-formsblocks native form submission, so the submit is a click handler on atype=buttonRelease
e7838fe), carrying phases 1 to 3