Repository navigation
feat: serve the ai session toolset over mcp at /api/mcp - #11738
Conversation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
/review |
Pi Reviewcc @hugocasa Should address issues before merging: P1 —
|
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Re Pi review: P1 fixed in 991925b. The /review |
|
/review |
|
Claude finished @hugocasa's task in 1m 41s —— View job cc @hugocasa Mergeable, but should ideally address nits: the error shown when a token is refused at connect is stale ( What changed since the last reviewThe fix for the Pi review's P1 in 991925b is correct. The tag fence now holds on every tool call.
FindingsP2: the refusal message no longer matches Checked, no issue found
I did not read all of the roughly 12k lines of tool handlers line by line for whether they behave the same as AGENTS.md compliance
Test coverageBackend: the unit tests cover what is new and security-relevant:
No automated test checks that a tag-fenced token stays fenced through a session tool call. That needs a running server, and the author checked it by hand (empty Frontend: Manual verification still needed:
|
| let scope_config = parse_mcp_scopes(auth.scopes().unwrap_or(&[])) | ||
| .map_err(|e| ErrorData::internal_error(e, None))?; | ||
| if !admits(auth.scopes().unwrap_or(&[]), scope_config.all) { | ||
| return Err(ErrorData::invalid_request( |
There was a problem hiding this comment.
P2: the refusal message no longer matches admits. Since 991925b, admits also accepts mcp:all together with other mcp:* scopes or an if_jobs:filter_tags:* fence. "whose only scope is mcp:all" now describes a stricter rule than the one enforced. That misleads a user who is refused because of a variables:read:… scope, and one who reads the message as a reason to remove their tag fence.
| return Err(ErrorData::invalid_request( | |
| "This server needs an `mcp:all` token with no scope from outside `mcp:` other than a job tag filter (`if_jobs:filter_tags:*`)", |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
/review |
|
Claude finished @hugocasa's task in 19s —— View job Good to merge What changed since the last reviewThe only new commit is 3bb56e6. It fixes the P2 from the last review: the error shown when a token is refused at connect (
This now matches
The same rule now appears the same way in the FindingsNone. Everything else in the earlier reviews still holds and is not repeated here:
AGENTS.md complianceNo new comments, and the commit message follows the repo's conventional format. Nothing to flag. Test coverageBackend: the change is a string in an error message. Manual verification: two checks are still open from before.
To see the new wording, connect a token scoped |
Codex Reviewcc @hugocasa Should address issues before merging: [P1] Preserve oversized rebase changes before deleting the draft —
An isolated reproduction using the actual diff and truncation functions produced a 100,128-byte patch from a 100 KB change; the returned text lost its tail. The existing chat rebase implementation returns the full diff without this runner cap. Check the complete serialized response before deletion and preserve the draft when it exceeds the limit, or retain a recoverable snapshot with paginated reads. Test coverageAdded Rust unit tests cover admission, route confinement, and handler helpers; the frontend test checks generated-definition freshness. No new integration tests cover MCP transport, API authorization, or worker execution. Documentation changes need no automated tests. The PR author reports real-client, differential, raw-app, token, and token-dialog verification. I ran the isolated truncation reproduction; I did not rerun the project suites or exercise a running instance. After fixing the finding, rebase a stale draft containing over 87,500 bytes of edits through Verify the run-side tag fence with an Trigger drafts remain unexercised. Create, edit, and deploy one through MCP, then confirm the stored trigger configuration matches the draft. Configured data-table and DuckLake paths remain unexercised. Check schema reads, SQL and migrations with restricted roles, and an actual DuckLake pipeline run. AI-agent step runs and OAuth sign-in from Claude.ai or ChatGPT remain unexercised. Confirm a step completes with its supplied inputs, and an OAuth-connected client discovers and calls the workspace tools successfully. |
Pi Reviewcc @hugocasa Should address issues before merging: P1 —
|
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…vers Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ws on /api/mcp Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t a selection excludes Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Claude finished @hugocasa's task in 1m 42s —— View job Good to merge What changed since the last reviewThe last review was at 57d3c7e. The branch commits since then are fac4efa through a4a9ac3, plus a merge of main. Core rules now come first in the MCP prompt (
Links to drafts and deployed items (
Raw app preview-only view:
Login:
FindingsNone. Nothing in the new commits touches the auth surface: admission, AGENTS.md compliance
Test coverageBackend: the changes are link strings and a check of Frontend:
Manual verification still needed:
|
Codex ReviewGood to merge Reviewed the changes since the last completed review at Test coverageBackend unit tests cover admission, scope containment, route guards, and handler helpers. The latest link changes add no integration coverage. Frontend coverage includes generated-definition freshness; no component tests are expected. Documentation needs no automated tests. I ran Follow the returned script, flow, and agent draft links and confirm they show the saved drafts. Open an app preview link and confirm the preview, sidebar, and build logs start in the intended state without changing the saved sidebar preference. For a logged-out user with several workspaces, complete MCP sign-in and confirm it reaches consent directly without a workspace error toast. The author still lists trigger drafts, applying data-table migrations, AI-agent step runs, and OAuth sign-in from Claude.ai or ChatGPT as unexercised. Verify deployment, execution, and workspace/access restrictions on those paths. |
Pi ReviewGood to merge I reviewed the commits added since the last review ( What the new commits do, checked
Considered and dismissed
Test coverage
Manual verification still outstanding (carried from prior rounds, unchanged by these commits): the run side of the |
…space urls Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rted Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ging access Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t the new scopes Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
/review |
|
Claude finished @hugocasa's task in 1m 19s —— View job Good to merge What changed since the last reviewThe last review was at a4a9ac3. Since then there is a merge of main (da99dec) and four commits: e7cbcdc, d0f3ce5, b7487dc and f9edfc8. I reviewed the four commits. The merge brings in main's own changes and adds nothing to the MCP surface.
Down migration (d0f3ce5).
Token editor keeps scopes until the picker is used (b7487dc).
One JWT per tool call (f9edfc8).
Old approve body and advertised scopes (f9edfc8).
FindingsNone. Nothing else on the auth surface changed since the last review: admission, AGENTS.md compliance
Test coverageBackend:
Frontend: the picker and editor changes are in Svelte components, where the codebase does not expect component tests. Manual verification still needed:
|
Codex Reviewcc @hugocasa Mergeable, but should ideally address nits: reset MCP edit state for each token
Reviewed the four commits since Test coverageThe updated Rust tests cover the new scope’s containment and endpoint-tool refusal. Frontend component tests are not expected, but the repeated-edit scenario above needs verification. Documentation needs no automated tests. I ran After fixing the finding, use token A’s picker and cancel, then rename a legacy token B without touching its picker. Its stored scopes and callable endpoint tools should remain unchanged. The author still reports trigger drafts as unexercised. Deploy one and confirm the stored trigger configuration takes effect. Apply a data-table migration with the required worker and confirm the intended schema change completes. Run an AI-agent step with a real provider and confirm successful execution. Complete OAuth sign-in from Claude.ai or ChatGPT and confirm tool discovery and calls respect the selected workspace and access restrictions. |
Pi Reviewcc @hugocasa Mergeable, but should ideally address nits: doc comment misattached to Reviewed the four commits added since the last completed review ( FindingP2 — the doc comment above The insert placed Considered and dismissed
Test coverageBackend: the new unit tests cover the Frontend: the access-picker and token-editor changes are Svelte component state logic; the codebase does not expect component tests here, and Manual verification still needed (carried over, not exercised in the PR): OAuth sign-in from a real external client (Claude.ai / ChatGPT), trigger drafts end-to-end (deploy and confirm stored trigger config), applying a datatable migration through the data tools, and AI-agent step test runs against a live provider. For the specific new commits: connect a token created through the access picker at "Read only" to |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
/review |
|
Claude finished @hugocasa's task in 35s —— View job Good to merge What changed since the last reviewMy last review was at f9edfc8. Since then there are two commits, 6060fc3 and f40271a. Between them they fix the two P2s that Codex and Pi raised on that round. 6060fc3 resets the token editor for each token (Codex P2).
f40271a renames
FindingsNone. These commits don't change the rest of the auth surface: admission, AGENTS.md compliance
Test coverageBackend:
Frontend: the picker and editor changes are in Svelte components, where the codebase doesn't expect component tests. Manual verification still needed: Token editor: open token A's editor, change its access level and cancel. Then open token B, an old folder token that carries Per-workspace URL: create one token at "Read only" and one at "Read and run" from the picker. Their stored scopes should be Selection: a token made as "Scripts and flows as tools" should carry Carried over from earlier rounds:
|
Pi ReviewGood to merge I reviewed the full diff at the head ( The two latest commits are correct.
No new findings. The Considered and dismissed
Test coverageBackend: unit tests cover scope parsing/containment ( Frontend: the generated-definition freshness check ( Docs/config: the Manual verification still needed: (1) with an |
Codex ReviewGood to merge Reviewed the changes since the previous Codex review at Test coverageBackend unit tests cover scope parsing, containment and endpoint refusal. I ran all 11 scope tests in an isolated Frontend component tests are not expected. Generated MCP definitions have freshness coverage. Documentation needs no automated tests. I did not run the Cargo/frontend suites or exercise a running instance. The author reports broader backend, client and browser verification. Use token A’s picker and cancel, then rename legacy token B without touching its picker. B’s stored scopes and callable endpoint tools should remain unchanged. Verify read-only, read-and-run and selection tokens cannot list or directly call endpoint tools on the legacy URLs. Confirm legacy tokens retain their existing tools. Exercise rollback with the renamed The author still lists trigger drafts, data-table migrations and AI-agent step runs as unexercised. Verify their deployment or execution with the required features, workers and provider. Complete OAuth sign-in from Claude.ai or ChatGPT and confirm discovery and tool calls respect the selected workspace and access restrictions. |
|
✅ Review round clean @ f40271a |
This commit updates the EE repository reference after PR #877 was merged in windmill-ee-private. Previous ee-repo-ref: 9e960246d51401a2b0487dcffcadd0b43cd67d39 New ee-repo-ref: b3fd7d49f2ba58f5080fabc3a5686c34a0b2684c Automated by sync-ee-ref workflow.
|
🤖 Updated |
# Conflicts: # backend/ee-repo-ref.txt # frontend/src/lib/components/copilot/chat/global/core.ts

Summary
Makes
/api/mcpthe one URL for Windmill's MCP server, and gives it the AI session's toolset: an external client (Claude, ChatGPT, Cursor) can explore the workspace, run scripts and flows, write per-user drafts, test-run them, diff and deploy, as the in-app chat does.What a client gets depends on the token, chosen when it signs in or when the token is created:
/api/mcpmcp:allmcp:all mcp:read_runmcp:all mcp:read_onlymcp:favorites, folders,mcp:scripts:…,mcp:flows:…,mcp:hub:…; "All scripts/flows" is stored asmcp:scripts:* mcp:flows:*The workspaces a token reaches are chosen the same way: one (the token is bound to it, as before), several (
mcp:workspaces:a,b), or all of the user's, including ones joined later. Every session tool takes aworkspace_id.The existing URLs (
/api/mcp/w/{workspace}/mcp,/sse,/api/mcp/gateway) keep serving existing tokens exactly as before, endpoint scopes included. They are no longer shown in the UI.How it is built
/api/mcproutes on the token's scopes:mcp:allgoes to the session toolset (backend/windmill-mcp/src/server/session.rs), a selection goes to the existing tools server with its endpoint tools turned off (NoEndpointTools). The existing URLs never set that flag. Both servers keep their own code.get_instructionstexts, the trigger/schedule schemas and the raw-app templates are emitted from the GLOBAL chat's TypeScript intobackend/windmill-api/src/mcp/session/gen/bynpm run gen:mcp-session(mcpSessionGen.test.tsis also the freshness check). Sentences that promise the chat's argument form or a user confirmation are rewritten for MCP, and generation fails if a new one appears. The folder guidance is cut out of the prompt as its own pieces, so a single-workspace token gets the prompt as before byte for byte, and a multi-workspace token gets the rest plusget_workspace_context.backend/windmill-api/src/mcp/session/, one file per group, mirroringchat/global/core.ts. Each tool acts through the API as the caller, so permission checks are the API's own.AGENTS.mdsays a behaviour change on one side is mirrored on the other, and acargo testfails when a session tool is neither implemented nor listed as editor-only. Changes to the session merged on main while this was open (stale step locks, AI agent identity on deploy, folder and group reads,create_resource_type, workspace settings tools, app sandboxing and viewer data access, chat-enabled flows, datatable migration schemas) are mirrored.Ctx::requestcalls the server's fully layered axum router withoneshotand the same minted JWT, instead of a loopback HTTP request tobase_internal_url: same middleware, auth, handlers, permission checks and 30s timeout, no socket. The router is registered perbase_internal_url, so the backend tests' one-server-per-test processes stay on their own databases; with no router registered it falls back to HTTP. The per-runnable tools server still uses HTTP.get_instructionsrule and the default-run rule. For an MCP client the prompt builder now puts the core rules (drafts only, find and read first, deploy on request,get_instructionsbefore writing, test after editing, test-run versus run) right after the opening, ahead of the path conventions, and the rest follows under "More rules". The text is the same, only reordered; the chat's own prompt is byte-identical.open_page,askUserQuestion,update_user_instructions,search_files,read_file.get_runend with the run's page,init_app/app writes return the draft's editor (preview_url), and a deploy returns the deployed item's page. Links useBASE_URL, or the request's origin whileBASE_URLis unset. The MCP prompt, near its top, andinit_app's result ask the model to open the link in a browser the user can see from the conversation when it has one, and otherwise to give the user the link.deploy_workspace_item,delete_workspace_itemand the other tools that reach the deployed workspace are annotateddestructiveHint, standing in for the session's confirmation card.rebase_draftkeeps a draft it cannot return whole. It discards the stale draft and returns the diff as the only record of the edits; when that result would exceed the server's tool-result cap (87,500 bytes), the draft is kept and the user is sent to the editor instead.externaloption that drops mentions of editor-only tools and the run form. With it off, the chat's prompt is byte-identical to before.Auth and security (please look closely)
mcp:allonly, optionally narrowed bymcp:read_only,mcp:read_runormcp:workspaces:. A scope from outsidemcp:that confines routes or paths (variables:read:f/x/*) is refused; a job tag fence (if_jobs:filter_tags:*) is allowed and carried into every tool call.guarded_url) that refuses any path that does not stay on the route its handler wrote (dot segments,#, another workspace, an unlisted instance route). The same guard holds read-only tokens to the read-only route rule, and read-and-run tokens to it exceptPOST jobs/run/p/andjobs/run/f/(the preview routes take code, so they stay refused). Unit tests cover both. The JWT carries the caller's job provenance, so a job's token stays capped.mcp:read_onlyormcp:read_rungets no API endpoint tools on/api/mcp/w/{w}/mcpand/api/mcp/gatewayeither; without that, thesemcp:all-based tokens reached the full endpoint list there,getVariableon secrets included. A selection made as "Scripts and flows as tools" carries the markermcp:runnables_as_toolsfor the same effect, since its other scopes are the ones older selections have. The picker adds the marker and hides it; approval containment treats it as narrowing only. Tokens minted before this PR carry none of these scopes and keep their tools.mcp:workspaces:is enforced on both servers, the existing tools server's per-workspace URLs and gateway included, otherwise a listed token would reach any workspace through/api/mcp/w/{other}/mcp.mcp:read_onlyandmcp:read_runare honoured by the existing tools server too, as read-only there (it has no read-and-run).McpScopeConfig::contains) treats the workspace list,mcp:read_onlyandmcp:read_runas narrowing only: an approving token cannot drop them.mcp:read_only), because an OAuth grant carries scopes and no read-only flag. Tokens with the existing read-only column keep working.UI
/api/mcpURL, and one collapsed "Use a token instead of signing in". In account settings MCP is its own section above Tokens instead of a toggle inside it./oauth/mcp_authorize, as the root layout already did for a logged-in user.McpAccessPicker.svelte) in the token form, the OAuth sign-in page and the token editor, so editing a token's access can no longer flip it to another server's model. Under it, a collapsed "Tools the client gets" lists the tools of the chosen level (GET /api/mcp/toolset?access=). "Scripts and flows as tools" opens the existing picker without endpoints, "All scripts/flows" included, and needs one workspace.Migration
workspace_idonmcp_oauth_server_codeandmcp_oauth_refresh_tokenbecomes nullable, so a sign-in granting several or all workspaces mints a token bound to none. The down migration also deletes every token, refresh token and code carryingmcp:read_only,mcp:read_run,mcp:runnables_as_toolsormcp:workspaces:: a server without these scopes ignores them, which would widen the token.Fallback for endpoint-specific rights
Not in the UI any more. A token with
mcp:endpoints:…can still be created through the API and used on the existing URLs, which keep serving endpoint tools. Telemetry in this PR (mcp/endpoint_tool_call,mcp/runnable_tool_call,mcp_workspace/tool_call, EE registry in the companion PR) shows whether anyone still relies on them before they are dropped.Known differences from a session
diff against="parent_workspace"(fork vs parent) returns "not available on this server".get_instructions subject:"flow"has no live AI-provider catalogue section.ai_skillresources are offered (the session's on/off toggles live in the browser). User instructions are not included for the same reason.get_workspace_settings/set_workspace_settingsand the API refuses them; an operator's restrictions are enforced by the API on each call.init_appalways starts from the framework template, where the chat gives an operator builder a static page.Test plan
claude -pagainst/api/mcp): read, write, deploy, run, a failing read and a delete; identical results through the in-process path and the loopback HTTP path. A scripted pass of 28 calls (reads, writes, deploys, runs, test runs, failures, deletes) was also identical, error texts byte for byte.claude -p(3 tasks × 2 runs per arm: write and check a script, build an app, run an existing script). Baseline (prompt cut at 4096 by Claude Code): never calledget_instructions, and ran the deployed script for "run X". Core rules first:get_instructionsbefore the script and the app,test_run_scriptfor "run X" as the rules ask. A condensed 3.7k prompt with the rest on demand behaved the same and was dropped for this simpler shape. Links in every answer in all arms; cost per task is higher once the model reads the guides.mcp:endpoints:*; switching it to Read only wrotemcp:all mcp:read_only, and to a selectionmcp:favorites mcp:runnables_as_tools. Down migration run in a rolled-back transaction: deleted the 12 tokens with the new scopes, kept the others. The approve route accepts the oldworkspace_idbody./api/mcp: a selection token gets its typed tools (named after their last path segment, as on main) and no endpoint tools; anmcp:alltoken gets the toolset; the same selection token keeps its endpoint tools on/api/mcp/w/{w}/mcp; no token gives 401 withresource/api/mcp.workspace_id; the list enforced on the tools server's per-workspace URL and gateway.run_scriptandrun_flowran deployed items on a worker, including with apasswordargument, stored as an expiring$var:reference; writes and test runs refused), the same tokens on the tools server reading only.rebase_draft: a 146 KB stale draft is kept with an editor link; a small one is rebased and its diff returned.extraPerms,create_resource_type, workspace settings get/set and the non-admin refusal, stale lock drops, AI agent identity kept on deploy.cargo test:windmill-mcp(90), the MCP part ofwindmill-api(80),tests/mcp_*.rs,tests/ai_sessions.rs;check:fast(only the errors already on main); chat vitest (the failures are the same 43 as on main).Screenshots
left in draft: adds a migration, touches auth and token paths and has an EE companion, wants a human look before ready
🤖 Generated with Claude Code