Skip to content

fix(google): make Sheets read-only end-to-end (OAuth scope + MCP tool catalog)#188

Merged
ljagiello merged 3 commits into
mainfrom
fix/sheets-readonly-scope
May 5, 2026
Merged

fix(google): make Sheets read-only end-to-end (OAuth scope + MCP tool catalog)#188
ljagiello merged 3 commits into
mainfrom
fix/sheets-readonly-scope

Conversation

@ljagiello

@ljagiello ljagiello commented May 5, 2026

Copy link
Copy Markdown
Contributor

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:

  1. apps/link/src/providers/google-providers.ts — sheets OAuth scope changed from spreadsheets (write, unverified in the upstream GCP project) to spreadsheets.readonly. Keeps drive.readonly because workspace-mcp's list_spreadsheets tool queries Drive.
  2. packages/core/src/mcp-registry/registry-consolidated.ts — Sheets entry now describes itself as read-only, and a new per-service permissionLevel field on the spec switches the workspace-mcp launch from --tools sheets to --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/warning block page when connecting Sheets. The cause: Friday delegates OAuth through the Gemini CLI Workspace Extension's verified GCP project (a tactical shim per google-providers.ts:19-23), and that project's verified scope set does not include spreadsheets (write). Upstream feature-config.ts flags it as defaultEnabled: false with 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. spreadsheets was the only Friday-requested scope outside the verified set. (presentations write and tasks* 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:

  • Friday gets its own verified GCP project (called out as the long-term fix in google-providers.ts:19-23), or
  • Gemini extension applies for spreadsheets verification upstream.

QA — full per-provider verification

Static checks

  • deno task typecheck — 0 errors
  • deno task -f @atlas/link test src/providers/google-providers.test.ts — 2/2 passed
  • deno task -f @atlas/core test src/mcp-registry/ — 250/250 passed (registry test updated to accept either --tools or --permissions)
  • Pre-existing 3 failures in apps/atlasd/daemon-startup.test.ts are unrelated (groq/litellm credential resolution) — not introduced by this branch

Auth-URL audit (all 5 providers, scope query param on the redirect to Google)

Provider Scopes
google-calendar openid email calendar
google-drive openid email drive
google-docs openid email documents drive.readonly
google-gmail openid email gmail.modify
google-sheets openid email spreadsheets.readonly drive.readonly ← fixed

MCP tool catalog (each provider's launch config, MCP tools/list response)

Provider Launch Tools registered
calendar --tools calendar 7 (list_calendars, get_events, manage_event, create_calendar, query_freebusy, manage_focus_time, manage_out_of_office)
drive --tools drive 14 (list/search/create/copy/update/manage permissions, etc.)
docs --tools docs 20 (search/create/edit/format/comment/export, etc.)
gmail --tools gmail 14 (search/get/send/draft/labels/filters, etc.)
sheets --permissions sheets:readonly 5 (list_spreadsheets, get_spreadsheet_info, read_sheet_values, list_sheet_tables, list_spreadsheet_comments) ← was 13 with 8 write tools, now read-only only

Browser OAuth flow (every provider, end-to-end on personal @gmail)

Ran Link locally on this branch, used a personal @gmail.com account (matches the original report's account class), walked the full OAuth consent flow for each of the 5 providers. All 5 reached accounts.google.com/signin/oauth/v2/consentsummary (legitimate consent), not accounts.google.com/signin/oauth/warning (the block page).

Provider Consent screen wording
calendar "See, edit, share, and permanently delete all the calendars you can access using Google Calendar"
drive "See, edit, create, and delete all of your Google Drive files"
docs "See and download all your Google Drive files" + "See, edit, create, and delete all your Google Docs documents"
gmail "Read, compose, and send emails from your Gmail account"
sheets "See and download all your Google Drive files" + "See all your Google Sheets spreadsheets" (read-only wording — fix verified)

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 MCP tools/call JSON-RPC.

MCP tool Result
list_spreadsheets ✓ "Successfully listed 3 spreadsheets for jagiello.lukasz@gmail.com" (Drive API call worked → confirms drive.readonly is sufficient)
get_spreadsheet_info ✓ "Spreadsheet: 'Urodzenia i zgony GUS' (ID: …)
read_sheet_values ✓ Range read returned (empty range, no error)
list_sheet_tables ✓ "No structured tables found"
list_spreadsheet_comments ✓ "No comments found"

Negative 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):

  • tokeninfo confirms granted scopes: email + drive.readonly + spreadsheets.readonly + userinfo.email + openid
  • Drive files.list (mimeType=spreadsheet) → 200 with real files
  • Sheets spreadsheets.get + values.get → 200 with metadata + range
  • Sheets values.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

  • The first revision of this PR set WORKSPACE_FEATURE_OVERRIDES=sheets.write:off on the workspace-mcp env. That env var doesn't exist in the Python workspace-mcp Friday actually launches via uvx — I'd confused it with the unrelated TypeScript workspace-server from gemini-cli-extensions. Caught by actually running the binary and listing tools. Replaced with --permissions sheets:readonly.
  • The first revision also dropped drive.readonly from the sheets OAuth scope. That broke list_spreadsheets (which queries Drive). Restored — both spreadsheets.readonly and drive.readonly are in the verified GCP project, so this doesn't reintroduce the original block.

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.
@ljagiello ljagiello requested a review from Vpr99 as a code owner May 5, 2026 04:06
ljagiello added 2 commits May 4, 2026 21:12
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).
@ljagiello ljagiello changed the title fix(link): drop unverified Google scopes — sheets read-only fix(google): make Sheets read-only end-to-end (OAuth scope + MCP tool catalog) May 5, 2026
@ljagiello ljagiello merged commit 3b946b7 into main May 5, 2026
9 checks passed
@ljagiello ljagiello deleted the fix/sheets-readonly-scope branch May 5, 2026 04:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant