Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions app/src/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
57 changes: 44 additions & 13 deletions app/src/server/pi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -267,21 +267,52 @@ export class Pi extends Think<Env, PiState> {
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. */
Expand Down
Loading