-
-
Notifications
You must be signed in to change notification settings - Fork 543
Description
Bug
When running in streamable-http transport mode, the OAuth 2.1 code path in service_decorator.py does not persist refreshed credentials back to the LocalDirectoryCredentialStore. This causes daily re-authentication when Google rotates refresh tokens.
Root Cause
The per-request flow in service_decorator.py (line ~292) retrieves credentials via OAuth21SessionStore.get_credentials_with_validation(), which creates a fresh Credentials object from the in-memory session store. When the google-auth library auto-refreshes the access token (and Google rotates the refresh token), the updated tokens only exist on that ephemeral Credentials object.
The persistence code in google_auth.py:get_credentials() (lines ~789-792) correctly calls credential_store.store_credential() after refresh — but this code path is not used by the OAuth 2.1 service decorator.
Meanwhile, save_credentials_to_session() logs: "Could not save credentials to session store - no user email found for session" — confirming the in-memory session store also fails to update.
Impact
- The on-disk credential file in
~/.google_workspace_mcp/credentials/goes stale after the initial auth - When Google rotates the refresh token (common on Workspace accounts with
refresh_token_expires_in: 604799/ 7 days), the old on-disk refresh token is revoked - On server restart (or when the in-memory token expires), the server loads the stale file and cannot refresh → user must re-authenticate daily
Reproduction
- Run
workspace-mcp --transport streamable-httpwith a Google Workspace account - Authenticate and make API calls
- Note the modified timestamp on
~/.google_workspace_mcp/credentials/<email>.json - Wait for the access token to expire (~1 hour) and make more API calls (tokens refresh in memory)
- Restart the server
- Observe that the credential file timestamp hasn't changed since step 2
- If the refresh token was rotated during step 4, authentication fails
Suggested Fix
In service_decorator.py, after build(service_name, version, credentials=credentials) returns (or via a post-request hook), check if credentials were refreshed and persist them:
service = build(service_name, version, credentials=credentials)
# Persist refreshed credentials to file store
if user_google_email and credentials.token:
from auth.credential_store import get_credential_store
credential_store = get_credential_store()
credential_store.store_credential(user_google_email, credentials)Note: since google-auth refreshes lazily (during the first API call, not during build()), a more robust approach would be to wrap Credentials.refresh() or add a token-refresh callback that triggers persistence.
Environment
workspace-mcpv1.14.3- Transport:
streamable-http - Google Workspace account with refresh token rotation enabled
- macOS, running via LaunchAgent
Workaround
Monkey-patching google.oauth2.credentials.Credentials.refresh() to write updated tokens to the credential store directory after each refresh.