From 25c422ba7e30f9e4bfb34ddeff88ce4bd6b2773e Mon Sep 17 00:00:00 2001 From: Dotta <34892728+cryppadotta@users.noreply.github.com> Date: Wed, 30 Sep 2026 13:10:42 -0500 Subject: [PATCH] fix(apps): request minimal Google service scopes (#14740) ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Google connections give agents service-specific tools through governed credentials. > - Each connection has a reviewed OAuth scope set. > - Docs, Sheets, and Slides request Drive permissions in addition to their own service scopes. > - Google documents these permissions as alternatives, not combined requirements. > - This pull request removes those extra permissions and redundant Calendar write free/busy access. > - Users grant fewer permissions without adding tools or changing connection access policy. ## Linked Issues or Issue Description Refs #13820. Related #14739 changes other connector permissions; it does not reduce these Google profiles. Companion broker PR: https://github.com/paperclipai/paperclip-cloud/pull/615. Ship the matching changes together after fresh-grant validation. **What happened?** Seven Google profiles request redundant scopes. Docs, Sheets, and Slides request Drive scopes. Calendar write requests free/busy even though calendar.events authorizes its availability tool. **Expected behavior** Each profile requests only the scopes required for its reviewed tools. Managed and customer-owned OAuth methods use the same set. **Steps to reproduce** Inspect the Google profile registry and the four app definitions on the base commit. Compare their scope sets with Google's MCP authorization alternatives linked in the updated documentation. **Paperclip version or commit** Base: 94e8dec56b359f639ea7b37c577115bc347d062b. **Deployment mode** Hosted and self-hosted Google Workspace connections. ## What Changed - Docs, Sheets, and Slides read profiles request only their service read-only scope. Write profiles request only their service write scope. - Calendar write keeps calendar-list read-only and event access. Calendar read keeps free/busy. - Apply these sets to managed and customer-owned OAuth methods and their exact-scope tests. - Document scope rationale, old-grant behavior, and the coordinated broker rollout. - Preserve the 21-scope integration union. Drive and Workspace Search still need their own Drive permissions. ## Verification - `pnpm exec vitest run packages/shared/src/app-definitions.test.ts`: 25 passed. - `pnpm -r typecheck`: passed with repository-pinned pnpm 9.15.4 after refreshing locked dependencies. - `pnpm build`: passed with repository-pinned pnpm 9.15.4. - `pnpm test:run`: attempted on the machine's Node 26 runtime, then interrupted after unrelated suite/collection failures. This is NOT a passing full-local-suite result. CI uses Node 24. - Node 24 focused diagnostic rerun: `workspace-runtime-exposure.test.ts` and `paperclip-control-plane-port.test.ts`: 43 passed, 3 skipped. The first suite hit a local preview-readiness timeout in CI; the failed shard passed its single retry without code changes. - Node 24 rerun of the three local assertion-failure suites (`chat-discord-adapter-patch`, `ai-connections`, and `heartbeat-active-run-output-watchdog`): 133 passed. These files were not changed. - Node 24 rerun of the changed manifest contract suite: 25 passed. - Latest-head GitHub checks are green at `da85dcbc89c44d88f3cabd5ada142c922fa51a3c`: 54 passed, 2 intentionally skipped; no failed or pending checks. Includes typecheck, build, general/serialized tests, all 8 browser E2E shards, Runner verification, and canary packaging. Greptile 5/5, no review threads. - Cross-repository comparison: all 16 app and broker profiles match exactly. - `git diff --check`: passed. - Google definitions are reviewed JSON source inputs preserved by the ingestion script. The generated TypeScript registry imports them; no generic provider regeneration is needed. - Fresh minimal-grant provider testing is outstanding. No new video was recorded, and existing broader credentials are not proof of minimal authorization. ## Risks The Cloud broker must ship the matching exact scope sets. Mixed versions fail closed. Validate fresh grants in staging before production. Existing provider tokens are not retroactively narrowed; affected managed connections must reconnect if their grants retain extra scopes or omit explicit scope evidence at refresh. Do not revoke the shared Google project to migrate one connection. This change does not grant access, add tools, or alter the Google Chat unread-filter block. ## Model Used OpenAI Codex, GPT-5-based coding agent, with reasoning, code execution, and browser tools. The exact runtime model ID and context-window size were not exposed to the agent. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge Co-authored-by: Paperclip --- doc/connections/GOOGLE-WORKSPACE.md | 34 +++++++++++++++++++ packages/shared/src/app-definitions.test.ts | 31 ++++------------- .../src/app-definitions/google-calendar.json | 2 -- .../src/app-definitions/google-docs.json | 6 ---- .../src/app-definitions/google-sheets.json | 6 ---- .../src/app-definitions/google-slides.json | 6 ---- .../shared/src/google-workspace-connectors.ts | 14 ++++---- 7 files changed, 47 insertions(+), 52 deletions(-) diff --git a/doc/connections/GOOGLE-WORKSPACE.md b/doc/connections/GOOGLE-WORKSPACE.md index 74187eee43..98b256a4d8 100644 --- a/doc/connections/GOOGLE-WORKSPACE.md +++ b/doc/connections/GOOGLE-WORKSPACE.md @@ -159,6 +159,40 @@ operations. Write profiles add only the reviewed write operations for their app; destructive or unreviewed tools do not become available merely because Google adds them upstream. +### Service-specific minimum scopes (2026-09-30) + +Docs, Sheets, and Slides request only their service's read-only scope in the +read profile and its read/write scope in the write profile. They do not also +request `drive.readonly` or `drive.file`: those are authorization alternatives, +not additional requirements. Drive and Workspace Search retain their separate +Drive permissions. Calendar write requests `calendar.calendarlist.readonly` +and `calendar.events`; the latter also authorizes `suggest_time`. Calendar read +retains `calendar.events.freebusy` alongside list/event read-only access. + +References: Google's [Docs read](https://developers.google.com/workspace/docs/api/reference/mcp/tools_list/read_doc) +and [update](https://developers.google.com/workspace/docs/api/reference/mcp/tools_list/update_doc), +[Sheets read](https://developers.google.com/workspace/sheets/api/reference/mcp/tools_list/get_spreadsheet) +and [update](https://developers.google.com/workspace/sheets/api/reference/mcp/tools_list/update_values), +[Slides read](https://developers.google.com/workspace/slides/api/reference/mcp/tools_list/read_presentation) +and [update](https://developers.google.com/workspace/slides/api/reference/mcp/tools_list/update_presentation), +and [Calendar suggest_time](https://developers.google.com/workspace/calendar/api/v3/reference/mcp/tools_list/suggest_time). + +The write scopes support editing existing accessible files by ID. `drive.file` +is a valid narrower alternative for app-authorized files, but would require a +different per-file authorization workflow. Read-only profiles remain separate; +the project-wide union is not the scope set requested by every connection. + +Deploy with the matching Cloud broker registry and test fresh grants in staging +before production. Signed authorization and refresh requests use exact scope +sets, so mixed versions fail closed. The broker rejects old broader grants and +refresh responses without explicit scope evidence for these reduced profiles. +Reconnect affected connections; do not relabel or globally revoke existing +tokens. Customer-owned methods request the same reduced sets on new consent; +previously issued grants are not retroactively narrowed. The 21-scope integration +union is unchanged by these profile-level reductions. Console must still keep +Drive/free-busy entries required by other profiles and separately used identity +scopes. Fresh provider proof is a release requirement, not implied by unit tests. + ### Google Chat scope reduction (2026-09-22) `chat.read` requests only `chat.spaces.readonly` and `chat.messages.readonly`. diff --git a/packages/shared/src/app-definitions.test.ts b/packages/shared/src/app-definitions.test.ts index 9333e1c196..fea1cde846 100644 --- a/packages/shared/src/app-definitions.test.ts +++ b/packages/shared/src/app-definitions.test.ts @@ -71,7 +71,7 @@ const GOOGLE_WORKSPACE_PROFILE_EXPECTATIONS = [ serverUrl: "https://docsmcp.googleapis.com/mcp/v1", capability: "read", riskTier: "S3", - scopes: [googleScope("drive.readonly"), googleScope("documents.readonly")], + scopes: [googleScope("documents.readonly")], writeTools: [], }, { @@ -80,11 +80,7 @@ const GOOGLE_WORKSPACE_PROFILE_EXPECTATIONS = [ serverUrl: "https://docsmcp.googleapis.com/mcp/v1", capability: "write", riskTier: "S4", - scopes: [ - googleScope("drive.readonly"), - googleScope("drive.file"), - googleScope("documents"), - ], + scopes: [googleScope("documents")], writeTools: ["update_doc"], }, { @@ -93,10 +89,7 @@ const GOOGLE_WORKSPACE_PROFILE_EXPECTATIONS = [ serverUrl: "https://sheetsmcp.googleapis.com/mcp/v1", capability: "read", riskTier: "S3", - scopes: [ - googleScope("drive.readonly"), - googleScope("spreadsheets.readonly"), - ], + scopes: [googleScope("spreadsheets.readonly")], writeTools: [], }, { @@ -105,11 +98,7 @@ const GOOGLE_WORKSPACE_PROFILE_EXPECTATIONS = [ serverUrl: "https://sheetsmcp.googleapis.com/mcp/v1", capability: "write", riskTier: "S4", - scopes: [ - googleScope("drive.readonly"), - googleScope("drive.file"), - googleScope("spreadsheets"), - ], + scopes: [googleScope("spreadsheets")], writeTools: [ "update_spreadsheet", "update_values", @@ -123,10 +112,7 @@ const GOOGLE_WORKSPACE_PROFILE_EXPECTATIONS = [ serverUrl: "https://slidesmcp.googleapis.com/mcp/v1", capability: "read", riskTier: "S3", - scopes: [ - googleScope("drive.readonly"), - googleScope("presentations.readonly"), - ], + scopes: [googleScope("presentations.readonly")], writeTools: [], }, { @@ -135,11 +121,7 @@ const GOOGLE_WORKSPACE_PROFILE_EXPECTATIONS = [ serverUrl: "https://slidesmcp.googleapis.com/mcp/v1", capability: "write", riskTier: "S4", - scopes: [ - googleScope("drive.readonly"), - googleScope("drive.file"), - googleScope("presentations"), - ], + scopes: [googleScope("presentations")], writeTools: ["update_presentation"], }, { @@ -163,7 +145,6 @@ const GOOGLE_WORKSPACE_PROFILE_EXPECTATIONS = [ riskTier: "S4", scopes: [ googleScope("calendar.calendarlist.readonly"), - googleScope("calendar.events.freebusy"), googleScope("calendar.events"), ], writeTools: [ diff --git a/packages/shared/src/app-definitions/google-calendar.json b/packages/shared/src/app-definitions/google-calendar.json index b8b00444c1..ab9e93999d 100644 --- a/packages/shared/src/app-definitions/google-calendar.json +++ b/packages/shared/src/app-definitions/google-calendar.json @@ -129,7 +129,6 @@ "serverUrl": "https://calendarmcp.googleapis.com/mcp/v1", "scopesHint": [ "https://www.googleapis.com/auth/calendar.calendarlist.readonly", - "https://www.googleapis.com/auth/calendar.events.freebusy", "https://www.googleapis.com/auth/calendar.events" ] }, @@ -165,7 +164,6 @@ "metadataUrl": "https://accounts.google.com/.well-known/openid-configuration", "scopesHint": [ "https://www.googleapis.com/auth/calendar.calendarlist.readonly", - "https://www.googleapis.com/auth/calendar.events.freebusy", "https://www.googleapis.com/auth/calendar.events" ], "oauthAuthorizationParams": { diff --git a/packages/shared/src/app-definitions/google-docs.json b/packages/shared/src/app-definitions/google-docs.json index bb6eb24a18..71a66e01f7 100644 --- a/packages/shared/src/app-definitions/google-docs.json +++ b/packages/shared/src/app-definitions/google-docs.json @@ -52,7 +52,6 @@ "defaults": { "serverUrl": "https://docsmcp.googleapis.com/mcp/v1", "scopesHint": [ - "https://www.googleapis.com/auth/drive.readonly", "https://www.googleapis.com/auth/documents.readonly" ] }, @@ -86,7 +85,6 @@ "tokenEndpoint": "https://oauth2.googleapis.com/token", "metadataUrl": "https://accounts.google.com/.well-known/openid-configuration", "scopesHint": [ - "https://www.googleapis.com/auth/drive.readonly", "https://www.googleapis.com/auth/documents.readonly" ], "oauthAuthorizationParams": { @@ -127,8 +125,6 @@ "defaults": { "serverUrl": "https://docsmcp.googleapis.com/mcp/v1", "scopesHint": [ - "https://www.googleapis.com/auth/drive.readonly", - "https://www.googleapis.com/auth/drive.file", "https://www.googleapis.com/auth/documents" ] }, @@ -163,8 +159,6 @@ "tokenEndpoint": "https://oauth2.googleapis.com/token", "metadataUrl": "https://accounts.google.com/.well-known/openid-configuration", "scopesHint": [ - "https://www.googleapis.com/auth/drive.readonly", - "https://www.googleapis.com/auth/drive.file", "https://www.googleapis.com/auth/documents" ], "oauthAuthorizationParams": { diff --git a/packages/shared/src/app-definitions/google-sheets.json b/packages/shared/src/app-definitions/google-sheets.json index 83e0ef0d31..de87b58007 100644 --- a/packages/shared/src/app-definitions/google-sheets.json +++ b/packages/shared/src/app-definitions/google-sheets.json @@ -53,7 +53,6 @@ "defaults": { "serverUrl": "https://sheetsmcp.googleapis.com/mcp/v1", "scopesHint": [ - "https://www.googleapis.com/auth/drive.readonly", "https://www.googleapis.com/auth/spreadsheets.readonly" ] }, @@ -87,7 +86,6 @@ "tokenEndpoint": "https://oauth2.googleapis.com/token", "metadataUrl": "https://accounts.google.com/.well-known/openid-configuration", "scopesHint": [ - "https://www.googleapis.com/auth/drive.readonly", "https://www.googleapis.com/auth/spreadsheets.readonly" ], "oauthAuthorizationParams": { @@ -128,8 +126,6 @@ "defaults": { "serverUrl": "https://sheetsmcp.googleapis.com/mcp/v1", "scopesHint": [ - "https://www.googleapis.com/auth/drive.readonly", - "https://www.googleapis.com/auth/drive.file", "https://www.googleapis.com/auth/spreadsheets" ] }, @@ -164,8 +160,6 @@ "tokenEndpoint": "https://oauth2.googleapis.com/token", "metadataUrl": "https://accounts.google.com/.well-known/openid-configuration", "scopesHint": [ - "https://www.googleapis.com/auth/drive.readonly", - "https://www.googleapis.com/auth/drive.file", "https://www.googleapis.com/auth/spreadsheets" ], "oauthAuthorizationParams": { diff --git a/packages/shared/src/app-definitions/google-slides.json b/packages/shared/src/app-definitions/google-slides.json index ccde0900e2..289366009d 100644 --- a/packages/shared/src/app-definitions/google-slides.json +++ b/packages/shared/src/app-definitions/google-slides.json @@ -52,7 +52,6 @@ "defaults": { "serverUrl": "https://slidesmcp.googleapis.com/mcp/v1", "scopesHint": [ - "https://www.googleapis.com/auth/drive.readonly", "https://www.googleapis.com/auth/presentations.readonly" ] }, @@ -86,7 +85,6 @@ "tokenEndpoint": "https://oauth2.googleapis.com/token", "metadataUrl": "https://accounts.google.com/.well-known/openid-configuration", "scopesHint": [ - "https://www.googleapis.com/auth/drive.readonly", "https://www.googleapis.com/auth/presentations.readonly" ], "oauthAuthorizationParams": { @@ -127,8 +125,6 @@ "defaults": { "serverUrl": "https://slidesmcp.googleapis.com/mcp/v1", "scopesHint": [ - "https://www.googleapis.com/auth/drive.readonly", - "https://www.googleapis.com/auth/drive.file", "https://www.googleapis.com/auth/presentations" ] }, @@ -163,8 +159,6 @@ "tokenEndpoint": "https://oauth2.googleapis.com/token", "metadataUrl": "https://accounts.google.com/.well-known/openid-configuration", "scopesHint": [ - "https://www.googleapis.com/auth/drive.readonly", - "https://www.googleapis.com/auth/drive.file", "https://www.googleapis.com/auth/presentations" ], "oauthAuthorizationParams": { diff --git a/packages/shared/src/google-workspace-connectors.ts b/packages/shared/src/google-workspace-connectors.ts index 7f0d42b496..5800309de1 100644 --- a/packages/shared/src/google-workspace-connectors.ts +++ b/packages/shared/src/google-workspace-connectors.ts @@ -18,14 +18,14 @@ export const GOOGLE_WORKSPACE_CONNECTOR_PROFILES: Readonly