Skip to content

fix(google-workspace): request minimal effective OAuth scopes (10 not 26)#421

Closed
JonasJesus42 wants to merge 1 commit into
mainfrom
JonasJesus42/google-workspace-minimal-scopes
Closed

fix(google-workspace): request minimal effective OAuth scopes (10 not 26)#421
JonasJesus42 wants to merge 1 commit into
mainfrom
JonasJesus42/google-workspace-minimal-scopes

Conversation

@JonasJesus42

@JonasJesus42 JonasJesus42 commented May 5, 2026

Copy link
Copy Markdown
Contributor

Symptom

Live testing returned "The caller does not have permission" on Calendar / Gmail / Drive / People, while Chat worked partially ("Requested entity was not found" — meaning the API authorized the call but found no data).

Root cause

The previous version asked for the union of every scope in each backend's PRM scopes_supported — 26 scopes total. Google's OAuth flow silently drops any requested scope that isn't pre-declared on the OAuth client's consent screen, so users were getting tokens covering only the subset that happened to be configured (chat-* in this case).

Fix

Replace the auto-union with a hand-curated minimal-effective set. Google's scope hierarchy means the broadest scope per service implicitly grants the narrower ones, so this still satisfies every tool we proxy:

Service Scopes (10 total)
Calendar calendar — covers all .readonly / .events.* variants
Chat chat.spaces, chat.messages, chat.memberships (orthogonal)
Drive drive — covers .readonly / .file
Gmail gmail.modify + gmail.compose — read + label + drafts; avoids the restricted mail.google.com/
People contacts.readonly, directory.readonly, userinfo.profile

Side benefits

  • Shorter consent screen (10 vs 26 scopes) → lower friction for users
  • Avoids mail.google.com/, which is "restricted" by Google and triggers their verification process
  • Clear least-privilege story

Still required (operational)

The OAuth client in Google Cloud Console must have these 10 scopes added to the consent screen. If any are missing, Google still drops them silently and the same symptom returns. To diagnose:

curl "https://oauth2.googleapis.com/tokeninfo?access_token=<TOKEN>" | jq .scope

The returned scope string is exactly what the user's token has. If it's missing entries from the list above, the consent screen needs those scopes added.

Test plan

  • bun scripts/check.ts google-workspace — passes
  • After deploy: re-authenticate with a test user and verify tokeninfo shows all 10 scopes
  • Smoke test one tool per service:
    • calendar_list_events { calendarId: "primary" }
    • gmail_search_threads { query: "is:unread newer_than:1d" }
    • drive_list_recent_files {}
    • chat_search_conversations { displayName: "..." }
    • people_get_user_profile {}

🤖 Generated with Claude Code


Summary by cubic

Request a minimal set of 10 Google Workspace OAuth scopes instead of the prior 26-scope union. This fixes “The caller does not have permission” errors and shortens the consent screen.

  • Bug Fixes

    • Replace auto-union of PRM scopes with a curated set of broad scopes per service to avoid Google silently dropping undeclared scopes.
    • Coverage: Calendar, Drive (full); Gmail (modify + compose); Chat (spaces, messages, memberships); People (contacts.readonly, directory.readonly, userinfo.profile).
    • Avoids the restricted mail.google.com/ scope.
  • Migration

    • Add these 10 scopes to the OAuth consent screen in Google Cloud Console, then re-authenticate users.
    • Verify the new token includes all scopes via Google’s tokeninfo endpoint.

Written for commit 1a597b8. Summary will update on new commits.

…t 26)

Live testing returned "The caller does not have permission" on Calendar,
Gmail, Drive and People while Chat worked partially. The most likely
cause is that the OAuth client in Google Cloud Console didn't have the
26-scope union from the PRM `scopes_supported` arrays pre-approved on
its consent screen — Google silently drops any requested scope that
isn't declared on the client, so users got a token covering only the
subset that happened to be configured.

Replace the union with a hand-curated list of the broadest sensible
scope per service. Google's scope hierarchy means the broader scope
implicitly grants the narrower ones, so this still satisfies every
tool we proxy:

  - calendar (full)                        → covers .readonly / .events / etc.
  - chat.spaces, chat.messages,
    chat.memberships                       → orthogonal, .readonly are subsets
  - drive (full)                           → covers .readonly / .file
  - gmail.modify + gmail.compose           → read+label+drafts, no restricted scope
  - contacts.readonly,
    directory.readonly,
    userinfo.profile                       → orthogonal People scopes

Side benefits beyond the bug fix: shorter consent screen, less likely to
hit Google's verification process for "restricted" scopes (we now avoid
mail.google.com/), and clearer least-privilege story.

If Google adds a tool needing a scope not on this list, extend the array
for the relevant service. The list is hand-curated and not regenerated by
`bun run generate-tools`.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@JonasJesus42

Copy link
Copy Markdown
Contributor Author

Superseded by the workspace rewrite to bundle our REST-based Google MCPs (Calendar/Gmail/Drive/Docs/Sheets/Slides/Forms/Meet). The minimal-scopes change is moot once we no longer proxy Google's official MCPs from the workspace.

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