From dacfa9fe259decf12f335f8e74ddd7aa8188fc24 Mon Sep 17 00:00:00 2001 From: DIodide Date: Sat, 22 Aug 2026 14:53:25 -0400 Subject: [PATCH] Google Calendar as a PI connection (Google-hosted MCP, read-only OAuth) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds Google's remote Calendar MCP server as a toggleable app. OAuth 2.0 with a pre-registered client (Google does no dynamic registration): a custom provider fixes the redirect URI, injects offline access so a refresh token is issued, and persists tokens on the user's desk DO — consent happens once from My apps, and that user's chats reuse the tokens over DO RPC. The Worker forwards the fixed /oauth/google/callback to the signed-in user's desk. Scopes are read-only (calendar list, events, free/busy), matching PI's never-writes promise, and the system prompt tells the model so. Also removes dev fixture files that slipped into an earlier merge. Claude-Session: https://claude.ai/code/session_01MKLJUWk6biNAKXupHTTWn5 --- app/.dev.vars.example | 5 + app/README.md | 8 + app/dev-grid.html | 9 - app/public/logos/googlecalendar.svg | 7 + app/src/client/dev-grid.tsx | 10 - app/src/client/lib/desk.ts | 1 + app/src/client/lib/tools.ts | 1 + app/src/client/pages/AppsPage.tsx | 11 + app/src/client/real-fixture.json | 318 ---------------------------- app/src/server/gcal.ts | 82 +++++++ app/src/server/index.ts | 15 +- app/src/server/pi.ts | 124 ++++++++++- app/src/shared/apps.ts | 18 +- app/wrangler.jsonc | 7 +- 14 files changed, 268 insertions(+), 348 deletions(-) delete mode 100644 app/dev-grid.html create mode 100644 app/public/logos/googlecalendar.svg delete mode 100644 app/src/client/dev-grid.tsx delete mode 100644 app/src/client/real-fixture.json create mode 100644 app/src/server/gcal.ts diff --git a/app/.dev.vars.example b/app/.dev.vars.example index 6a2a104..e73ac76 100644 --- a/app/.dev.vars.example +++ b/app/.dev.vars.example @@ -13,3 +13,8 @@ SESSION_SECRET="" # OIT API store credentials (ActiveDirectory subscription) for alias->netid. OIT_CONSUMER_KEY="" OIT_CONSUMER_SECRET="" +# Google Calendar MCP (dev needs its own OAuth client with a +# http://localhost:5173/oauth/google/callback redirect URI). +GOOGLE_OAUTH_CLIENT_ID="" +GOOGLE_OAUTH_CLIENT_SECRET="" +APP_ORIGIN="" diff --git a/app/README.md b/app/README.md index 2027f68..4c6ebf5 100644 --- a/app/README.md +++ b/app/README.md @@ -48,6 +48,14 @@ browser (React + useAgentChat over WebSocket) full-page weekly Planner, an Agenda, and My apps. Design system in `src/client/styles.css`; official TigerApps logos in `public/logos/`. +- **Google Calendar**: connects to Google's hosted Calendar MCP server with + read-only scopes via OAuth (`src/server/gcal.ts`). The client is + pre-registered in Google Cloud; consent runs once from *My apps* against + the user's desk DO, which stores the tokens and shares them with that + user's chats. Requires the `GOOGLE_OAUTH_CLIENT_ID` var, the + `GOOGLE_OAUTH_CLIENT_SECRET` secret, and an authorized redirect URI of + `/oauth/google/callback` on the OAuth client. + ## Develop ```bash diff --git a/app/dev-grid.html b/app/dev-grid.html deleted file mode 100644 index f24ff68..0000000 --- a/app/dev-grid.html +++ /dev/null @@ -1,9 +0,0 @@ - - - grid test - - -
- - - diff --git a/app/public/logos/googlecalendar.svg b/app/public/logos/googlecalendar.svg new file mode 100644 index 0000000..a92bd31 --- /dev/null +++ b/app/public/logos/googlecalendar.svg @@ -0,0 +1,7 @@ + + + + + + 31 + diff --git a/app/src/client/dev-grid.tsx b/app/src/client/dev-grid.tsx deleted file mode 100644 index 88f664c..0000000 --- a/app/src/client/dev-grid.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { createRoot } from "react-dom/client"; -import { WeekGrid } from "./components/WeekGrid"; -import { extractSchedule } from "./lib/tools"; -import fixture from "./real-fixture.json"; -import "./styles.css"; - -const view = extractSchedule(fixture)!; -createRoot(document.getElementById("root")!).render( -
-); diff --git a/app/src/client/lib/desk.ts b/app/src/client/lib/desk.ts index 931ef48..0073075 100644 --- a/app/src/client/lib/desk.ts +++ b/app/src/client/lib/desk.ts @@ -6,6 +6,7 @@ import { userInstance } from "./auth"; export type DeskState = { settings: PiSettings | null; appErrors: Partial>; + authUrls?: Partial>; }; export type McpServersSnapshot = { diff --git a/app/src/client/lib/tools.ts b/app/src/client/lib/tools.ts index da27ced..2724241 100644 --- a/app/src/client/lib/tools.ts +++ b/app/src/client/lib/tools.ts @@ -485,4 +485,5 @@ export const APP_INK: Record = { princetoncourses: "var(--hl-orange)", path: "var(--hl-violet)", snatch: "var(--hl-pink)", + gcal: "var(--hl-mint)", }; diff --git a/app/src/client/pages/AppsPage.tsx b/app/src/client/pages/AppsPage.tsx index da95c12..4d650bc 100644 --- a/app/src/client/pages/AppsPage.tsx +++ b/app/src/client/pages/AppsPage.tsx @@ -41,6 +41,9 @@ export function AppsPage({ } function statusFor(key: AppKey): { text: string; cls: string } { + if (desk.deskState?.authUrls?.[key]) { + return { text: "one step left", cls: "status" }; + } const err = desk.deskState?.appErrors?.[key]; if (err) return { text: `couldn't connect — ${err}`, cls: "status err" }; const server = desk.mcp?.servers?.[key]; @@ -109,6 +112,14 @@ export function AppsPage({

{app.detail}

{status.text} + {on && desk.deskState?.authUrls?.[app.key] && ( + + Connect with Google → + + )}