From edfe2c484933dd9b596e5949a7e70405f117a635 Mon Sep 17 00:00:00 2001 From: jonnyparris <6400000+jonnyparris@users.noreply.github.com> Date: Tue, 26 May 2026 13:29:52 +0100 Subject: [PATCH] fix(mcp): use UserControl DO ID hex in OAuth callback path (avoid email-encoding mismatch) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After hitting "Redirect URI not allowed by application configuration" from cf-portal even after matching Seal's callback shape, dug into the actual wire traffic and found the bug: registered redirect_uri: https://dodo.../agents/coding-agent/ruskin.constant@gmail.com/callback authorize redirect_uri: https://dodo.../agents/coding-agent/ruskin.constant%40gmail.com/callback The OAuth client URL-encodes the redirect_uri when it goes into the authorize URL's query string (`%40` for `@`). cf-portal's authorize endpoint appears to do a strict string comparison against the as-registered redirect_uri and rejects the encoded form. Switch the instance segment from email to UserControl DO ID hex (`env.USER_CONTROL.idFromName(email).toString()`) which is a-z0-9 only — no encoding ambiguity. The /agents/* route handler doesn't parse the URL path for routing anyway (uses CF Access cookie to identify the user), so this changes the URL shape without any functional impact downstream. Tests: 817/817 pass. Typecheck clean. beep-boop-🤖 --- src/coding-agent.ts | 10 ++++++---- src/index.ts | 24 ++++++++++++++---------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/src/coding-agent.ts b/src/coding-agent.ts index 2afc1ec..8212936 100644 --- a/src/coding-agent.ts +++ b/src/coding-agent.ts @@ -6327,12 +6327,14 @@ export class CodingAgent extends Think { const url = server.server_url; const name = server.name ?? new URL(url).host; await this.removeMcpServer(mcpId); - // Mirror the /api/mcp/start-auth callback path shape (Seal pattern): - // /agents///callback. cf-portal rejects - // redirect URIs that don't follow this OAuth-callback shape. + // Mirror the /api/mcp/start-auth callback path shape. + // Use the UserControl DO ID hex (not email) to avoid URL-encoding + // mismatches when the OAuth client sends the redirect_uri as a query + // parameter — see the long comment in /api/mcp/start-auth. + const userId = this.env.USER_CONTROL.idFromName(this.name).toString(); await this.addMcpServer(name, url, { callbackHost: this.env.WORKER_URL, - callbackPath: `/agents/coding-agent/${this.name}/callback`, + callbackPath: `/agents/coding-agent/${userId}/callback`, }); } diff --git a/src/index.ts b/src/index.ts index 197ba8a..e0176c9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1636,17 +1636,21 @@ app.post("/api/mcp/start-auth", async (c) => { : inferredHost; // callbackPath follows the Agents-SDK convention used by Seal et al: // `/agents///callback`. The trailing - // /callback segment matters — some OAuth providers (cf-portal in - // particular) reject redirect URIs that don't end in a recognised - // OAuth-callback suffix with "Redirect URI not allowed by application - // configuration". Using the SDK's canonical shape also keeps us - // compatible with `/agents/*` routing on the way back. + // /callback segment matters — some OAuth providers reject redirect + // URIs that don't end in a recognised OAuth-callback suffix. // - // userEmail is already canonicalized (lowercased + trimmed) by the - // auth middleware so it's safe to embed in a URL path. Edge case: - // emails contain `@` and `.` which are valid in URL path segments - // per RFC 3986 — no encoding needed and no provider rejects them. - const callbackPath = `/agents/coding-agent/${userEmail}/callback`; + // Use the user's UserControl DO ID hex (not the email) as the + // instance segment. The `@` and `.` in an email — even though they + // are valid URL path characters per RFC 3986 — get URL-encoded by + // the OAuth client (`@` → `%40`) when included in the authorize + // request's redirect_uri query param. cf-portal's authorize endpoint + // appears to do a strict string comparison against the as-registered + // URI, so the encoded vs decoded forms don't match and the request + // is rejected with "Redirect URI not allowed by application + // configuration". Using a hex-only segment side-steps the encoding + // mismatch entirely. + const userId = c.env.USER_CONTROL.idFromName(userEmail).toString(); + const callbackPath = `/agents/coding-agent/${userId}/callback`; const result = await stub.addMcpServer(displayName, mcpUrl, { callbackHost, callbackPath,