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
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:
co-authored by
Claude Fable 5
parent
3724b68f23
commit
8d82d6c4ec
@@ -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),
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user