Let agents drive the flow API over MCP
Playwright Tests / test-playwright (1, 2) (push) Canceled after 0s
Playwright Tests / test-playwright (2, 2) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Test Backend / test-backend (push) Canceled after 0s
Compose Smoke Test / test-compose (push) Canceled after 0s
Playwright Tests / merge-reports (push) Canceled after 0s

The engine now speaks MCP at /mcp, with a built-in OAuth 2.1 authorization
server in front of it: an agent registers itself, sends a human to the browser
to approve it, and exchanges the resulting code for a token. PKCE is required,
codes are single-use and stored only as hashes, the browser is redirected to
the URI that was registered rather than the one asked for, and refresh tokens
rotate so that replaying a spent one revokes the whole line.

Twenty tools cover reading, building, publishing and running flows, and each
one calls the same REST endpoint the dashboard calls, in-process, carrying the
caller's own token. That keeps one description of what a flow is and what may
be done to it — validation, the draft/publish split, the version check — and
means an agent can do nothing a person could not do in the browser.

Agent tokens are RS256 with a keypair of their own rather than the secret that
signs browser sessions, so deleting the key withdraws every agent without
logging anyone out, and deps.decode_token grew the branch that trusting a
second issuer will need when the hosted login arrives.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Melvin Strobl
2026-08-16 00:22:41 +02:00
co-authored by Claude Fable 5
parent 3724b68f23
commit 8d82d6c4ec
28 changed files with 2459 additions and 555 deletions
+14 -1
View File
@@ -15,6 +15,17 @@ const isLoggedIn = () => {
return localStorage.getItem("access_token") !== null
}
/**
* Where to go after signing in.
*
* Only a path on this origin: an open redirect here would let a link take
* someone through a real login and land them somewhere else entirely.
*/
export function safeRedirect(target: string | undefined): string {
if (!target || !target.startsWith("/") || target.startsWith("//")) return "/"
return target
}
const useAuth = () => {
const navigate = useNavigate()
const queryClient = useQueryClient()
@@ -48,7 +59,9 @@ const useAuth = () => {
const loginMutation = useMutation({
mutationFn: login,
onSuccess: () => {
navigate({ to: "/" })
// The consent page sends people here with where to come back to.
const target = new URLSearchParams(window.location.search).get("redirect")
navigate({ to: safeRedirect(target ?? undefined) })
},
onError: handleError.bind(showErrorToast),
})