diff --git a/app/src/server/index.ts b/app/src/server/index.ts index d325a44..a0a6ab7 100644 --- a/app/src/server/index.ts +++ b/app/src/server/index.ts @@ -15,12 +15,19 @@ export default { // signed-in user's desk DO, which initiated the flow and holds the state. if (url.pathname === GCAL_CALLBACK_PATH) { const session = await getSession(request, env); - if (!session) return Response.redirect(`${url.origin}/`, 302); + if (!session) { + console.warn("oauth callback arrived without a session — bouncing home"); + return Response.redirect(`${url.origin}/`, 302); + } const desk = await getAgentByName( env.Pi, `${userPrefix(session.netid)}desk` ); - return desk.fetch(request); + const response = await desk.fetch(request); + console.log( + `oauth callback for ${session.netid}: desk answered ${response.status} → ${response.headers.get("location") ?? "(no redirect)"}` + ); + return response; } // Every agent route belongs to exactly one signed-in user: instance diff --git a/app/src/server/pi.ts b/app/src/server/pi.ts index 6db6b81..d14f914 100644 --- a/app/src/server/pi.ts +++ b/app/src/server/pi.ts @@ -267,21 +267,52 @@ export class Pi extends Think { const manager = this.mcp as unknown as { mcpConnections?: Record< string, - { options?: { transport?: { authProvider?: { authUrl?: string } } } } + { + connectionState?: string; + options?: { transport?: { authProvider?: { authUrl?: string } } }; + } >; + getServersFromStorage?: () => Array<{ + id: string; + name: string; + server_url: string; + client_id: string | null; + auth_url: string | null; + callback_url: string; + server_options: string | null; + }>; + saveServerToStorage?: (server: { + id: string; + name: string; + server_url: string; + client_id: string | null; + auth_url: string | null; + callback_url: string; + server_options: string | null; + }) => void; }; - const live = - manager.mcpConnections?.gcal?.options?.transport?.authProvider?.authUrl; - if (live) return live; - // Fallback: re-registering an existing server redeems a stored auth URL. - const retry = await this.addMcpServer("Google Calendar", GCAL_MCP_URL, { - id: "gcal", - callbackHost: this.appOrigin(), - callbackPath: GCAL_CALLBACK_PATH.slice(1), - transport: { type: "streamable-http" }, - }); - if (retry.state === "authenticating") return retry.authUrl; - return this.getMcpServers().servers.gcal?.auth_url ?? null; + const url = + manager.mcpConnections?.gcal?.options?.transport?.authProvider?.authUrl ?? + this.getMcpServers().servers.gcal?.auth_url ?? + null; + if (!url) { + console.warn("gcal: probe ran but no consent URL was produced"); + return null; + } + // The anonymous connect left the connection "ready", and the SDK's + // callback handler short-circuits ready connections as "auth accepted" + // WITHOUT exchanging the authorization code — so consent silently did + // nothing. Flip the connection into the authenticating state (live and + // persisted, so a fresh isolate at callback time restores the same way) + // to route the callback through the real code exchange. + const conn = manager.mcpConnections?.gcal; + if (conn) conn.connectionState = "authenticating"; + const row = manager + .getServersFromStorage?.() + .find((server) => server.id === "gcal"); + if (row) manager.saveServerToStorage?.({ ...row, auth_url: url }); + console.log("gcal: consent pending, connection marked authenticating"); + return url; } /** The per-user desk instance is the token authority for Google OAuth. */