fix(google): make Sheets read-only end-to-end (OAuth scope + MCP tool catalog)#188
Merged
Conversation
The `spreadsheets` (sheets write) scope is not registered in the Gemini CLI Workspace Extension's verified GCP project — it's `defaultEnabled: false` upstream with the comment "not in published GCP project". Any external user connecting Google Sheets hit Google's "This app is blocked" page (reported by Yena on a personal @gmail). Switch to `spreadsheets.readonly`, which is in the verified set, and drop the redundant `drive.readonly` (sheet content reads don't need Drive metadata browsing). Calendar, Drive, Docs, Gmail are unaffected. Follow-up needed: registry-consolidated.ts still describes Sheets as read/write and launches workspace-mcp without sheets.write disabled, so write tools will register but 403 at runtime. Tracking separately.
Pairs with the Link OAuth scope change in this branch. Without these edits, workspace-mcp would still register sheets write tools (sheets.update*, sheets.create*, sheets.format*) — they'd silently 403 at runtime because the OAuth token only carries spreadsheets.readonly. - Description and constraints now state read-only; explains why (verified GCP project doesn't include the spreadsheets write scope). - New per-service extraStartupEnv field on the spec, used to inject WORKSPACE_FEATURE_OVERRIDES=sheets.write:off so the MCP server doesn't expose write tools at all.
…e drive.readonly
Two corrections to the prior commits in this branch, caught by actually
launching workspace-mcp and inspecting the registered tool catalog:
1. The previous commit set WORKSPACE_FEATURE_OVERRIDES=sheets.write:off
on the workspace-mcp subprocess. That env var does not exist in the
Python `workspace-mcp` (taylorwilsdon/google_workspace_mcp) Friday
actually launches via uvx — it was confused with the unrelated
TypeScript `workspace-server` from gemini-cli-extensions. Result: the
override was silently ignored and write tools (modify_sheet_values,
create_spreadsheet, format_sheet_range, etc.) would still register
and 403 at runtime.
The correct knob is `--permissions sheets:readonly` (mutually exclusive
with `--tools`). Verified empirically:
`--tools sheets` → 13 tools (5 read + 8 write)
`--permissions sheets:readonly` → 5 tools (read only)
Spec gains an optional `permissionLevel` field; when set the launch
command uses `--permissions` instead of `--tools`.
2. The previous OAuth scope change dropped `drive.readonly` from sheets,
reasoning that `spreadsheets.readonly` was sufficient. Wrong — the
readonly tool `list_spreadsheets` queries Drive (it lists files of
mimeType=spreadsheet), so it requires drive.readonly to function.
Restored. Both scopes are in the verified GCP project so this doesn't
re-introduce the original block.
Updated registry test to accept either --tools or --permissions (since
they're mutually exclusive flags now used per-service).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two coordinated changes that together fully fix Google's "This app is blocked" error and avoid follow-on runtime 403s on Sheets write tools:
apps/link/src/providers/google-providers.ts— sheets OAuth scope changed fromspreadsheets(write, unverified in the upstream GCP project) tospreadsheets.readonly. Keepsdrive.readonlybecause workspace-mcp'slist_spreadsheetstool queries Drive.packages/core/src/mcp-registry/registry-consolidated.ts— Sheets entry now describes itself as read-only, and a new per-servicepermissionLevelfield on the spec switches the workspace-mcp launch from--tools sheetsto--permissions sheets:readonly. This is what actually filters the tool catalog: 13 tools → 5 (read only). Without (2), write tools would still register and silently 403.Calendar, Drive, Docs, Gmail are unaffected.
Why
A Friday user (personal @gmail) hit Google's
accounts.google.com/signin/oauth/warningblock page when connecting Sheets. The cause: Friday delegates OAuth through the Gemini CLI Workspace Extension's verified GCP project (a tactical shim pergoogle-providers.ts:19-23), and that project's verified scope set does not includespreadsheets(write). Upstreamfeature-config.tsflags it asdefaultEnabled: falsewith the comment "not in published GCP project" — confirmed by their v0.0.8 cleanup: gemini-cli-extensions/workspace#347.Audited the other Friday scopes against the upstream verified list.
spreadsheetswas the only Friday-requested scope outside the verified set. (presentationswrite andtasks*are also unverified, but Friday doesn't request either.)Trade-off
Sheets goes from advertised "read/write" to actually read-only. Path back to write:
google-providers.ts:19-23), orspreadsheetsverification upstream.QA — full per-provider verification
Static checks
deno task typecheck— 0 errorsdeno task -f @atlas/link test src/providers/google-providers.test.ts— 2/2 passeddeno task -f @atlas/core test src/mcp-registry/— 250/250 passed (registry test updated to accept either--toolsor--permissions)apps/atlasd/daemon-startup.test.tsare unrelated (groq/litellm credential resolution) — not introduced by this branchAuth-URL audit (all 5 providers, scope query param on the redirect to Google)
openid email calendaropenid email driveopenid email documents drive.readonlyopenid email gmail.modifyopenid email spreadsheets.readonly drive.readonly← fixedMCP tool catalog (each provider's launch config, MCP
tools/listresponse)--tools calendar--tools drive--tools docs--tools gmail--permissions sheets:readonlyBrowser OAuth flow (every provider, end-to-end on personal @gmail)
Ran Link locally on this branch, used a personal
@gmail.comaccount (matches the original report's account class), walked the full OAuth consent flow for each of the 5 providers. All 5 reachedaccounts.google.com/signin/oauth/v2/consentsummary(legitimate consent), notaccounts.google.com/signin/oauth/warning(the block page).End-to-end MCP execution against real Google APIs (sheets, the changed provider)
Granted the sheets OAuth flow on the personal @gmail account, captured the real bearer token, launched workspace-mcp with the exact launch config Friday's registry now produces (
--permissions sheets:readonly --transport streamable-http), and called each of the 5 read tools via MCPtools/callJSON-RPC.list_spreadsheetsdrive.readonlyis sufficient)get_spreadsheet_inforead_sheet_valueslist_sheet_tableslist_spreadsheet_commentsNegative test: attempted
modify_sheet_values(a write tool) — workspace-mcp returned"Unknown tool: 'modify_sheet_values'". Write tools are correctly filtered out by--permissions sheets:readonly.Direct Google API checks (sanity, before MCP run):
tokeninfoconfirms granted scopes:email + drive.readonly + spreadsheets.readonly + userinfo.email + openidfiles.list(mimeType=spreadsheet) → 200 with real filesspreadsheets.get+values.get→ 200 with metadata + rangevalues.update(write attempt) → 403 PERMISSION_DENIED, ACCESS_TOKEN_SCOPE_INSUFFICIENT (expected — proves write scope is correctly absent)Test token revoked after QA via
https://oauth2.googleapis.com/revoke.Notes for reviewers
WORKSPACE_FEATURE_OVERRIDES=sheets.write:offon the workspace-mcp env. That env var doesn't exist in the Pythonworkspace-mcpFriday actually launches via uvx — I'd confused it with the unrelated TypeScriptworkspace-serverfrom gemini-cli-extensions. Caught by actually running the binary and listing tools. Replaced with--permissions sheets:readonly.drive.readonlyfrom the sheets OAuth scope. That brokelist_spreadsheets(which queries Drive). Restored — bothspreadsheets.readonlyanddrive.readonlyare in the verified GCP project, so this doesn't reintroduce the original block.