fix: Google Workspace MCP connectors - durable refresh tokens, reliable OAuth, honest connect state (supersedes #185)
From the PR description
Supersedes #185 by @hordruma (whose two commits are carried verbatim with authorship preserved), plus @willchen96's two follow-up fixes from that branch, the review-fix commits from hordruma/mike#1, and one new commit. Rebased linearly onto current main (20c0ed2) - #185 had drifted 72 commits behind.
What's here, by author
@hordruma (from #185):
- Google only issues a refresh token when the authorization request carries
access_type=offline&prompt=consent; without them a Google connector authorizes once and breaks when the short-lived access token expires. These provider-specific params are now injected at the one point the MCP SDK exposes the built URL. - OAuth completion switched from COOP-broken popup postMessage signals to backend polling.
@willchen96 (from the #185 branch):
- Concise server permission errors surfaced to the user; sanitized tool-error response logging.
Review fixes (from hordruma/mike#1):
oauthConnectedis now an honest completion signal - starting OAuth no longer reads as connected, and a stale token row is invalidated when an interactive redirect is required, so the frontend poll can't resolve on a phantom success and close the consent popup mid-flow.- Google host matching handles absolute (
googleapis.com.) hostnames. - Transport-error content is wrapped as untrusted before it can reach a prompt; audit rows record the real response size.
- The connectors-page OAuth wait is cancelable: escape hatch, unmount cleanup, and polling backoff.
New in this PR:
- Fail-fast setup guard: Google offers no dynamic client registration, so on deployments without
GOOGLE_MCP_OAUTH_CLIENT_ID/_SECRETthe flow previously died deep in the SDK with an unactionable error. It now fails immediately with the exact Google Cloud Console steps and the deployment's actual redirect URI in the message (the route surfaces it asdetail, so the UI shows real instructions). The env vars are now documented inbackend/.env.example- previously they were read by the code but documented nowhere.
Verification
- Rebase conflicts resolved to the branch's proven end-state (test-runner scripts, vitest test rewrite).
backend/: tsc clean; 447 tests pass (includes the new fail-fast test and the branch's oauth suite).frontend/: tsc clean; 160 tests pass (includes the connectors polling/cancel suites).- Live against real Google endpoints (local backend, test client id): connector create →
POST .../oauth/startperforms real discovery and returns anaccounts.google.comauthorization URL carrying the configured client, PKCE S256, andaccess_type=offline&prompt=consent;oauthConnectedstaysfalseuntil genuine completion. - The consent → token-exchange last mile requires a real Google OAuth client; a manual end-to-end pass with real credentials is planned as follow-up validation.
If @hordruma returns, happy to hand this back - it exists so the fix isn't blocked on an unresponsive branch.
🤖 Generated with Claude Code
Our analysis
Harden Google MCP OAuth setup and completion — read the full analysis →
Think the analysis missed something the PR description covers?
Capture this PR into my fork
Download a Markdown prompt that tells Claude how to port every
commit in this PR into your working tree. Run it via
claude -p < capture-pull-285.md from
inside the repo you want the changes in.