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,