Skip to content

Conversation

@cfdude
Copy link
Contributor

@cfdude cfdude commented Nov 6, 2025

Problem

When using this MCP server in Claude Desktop and switching between accounts (e.g., from a Pro account to a Teams account, or Teams to Enterprise), users encounter a critical authentication error:

Invalid or expired OAuth state parameter

Root Cause

Claude Desktop restarts the MCP server process when switching between accounts. The OAuth authentication flow works as follows:

  1. User initiates authentication → MCP server generates a state parameter and stores it in-memory
  2. User clicks auth link → redirects to Google for authentication
  3. Claude Desktop switches accounts → MCP server process restarts → in-memory state is lost
  4. Google redirects back with the authorization code and state parameter
  5. MCP server tries to validate state → fails because the state was lost during restart

This makes it impossible to complete authentication when switching between Claude Desktop accounts.

Solution

This PR implements persistent file-based OAuth state storage that survives process restarts.

Changes

  1. New file: auth/oauth_state_store.py

    • Implements PersistentOAuthStateStore class
    • Stores OAuth states in ~/.google_workspace_mcp/credentials/oauth_states.json
    • Thread-safe operations using RLock
    • Automatic cleanup of expired states
    • Handles datetime serialization/deserialization for JSON storage
  2. Modified: auth/oauth21_session_store.py

    • Integrates persistent storage into existing OAuth session management
    • Falls back to in-memory storage if persistent storage fails
    • Comprehensive logging with [OAUTH21] and [PERSISTENT STORE] prefixes

Key Features

  • Process restart resilient: State survives MCP server restarts
  • Thread-safe: Uses RLock for concurrent access
  • Auto-cleanup: Expired states are automatically removed
  • Backward compatible: Falls back to in-memory storage if persistent storage unavailable
  • Security: States are consumed (one-time use) to prevent replay attacks
  • Configurable expiry: States expire after 10 minutes by default

Testing

Tested and verified working:

  1. ✅ Start authentication in Claude Desktop Pro account
  2. ✅ Click auth link while MCP server is running
  3. ✅ Switch to Claude Desktop Teams account (triggers MCP restart)
  4. ✅ Complete Google authentication
  5. ✅ Successfully returns to Claude Desktop with valid credentials

The persistent state storage successfully validated the state parameter after the restart.

Use Case Impact

This fix is critical for Claude Desktop users who:

  • Use multiple Claude account types (Pro, Teams, Enterprise)
  • Switch between accounts during the same session
  • Need reliable authentication across account switches

Without this fix, users must:

  • Avoid switching accounts during OAuth flows
  • Manually restart Claude Desktop to clear state
  • Experience authentication failures frequently

Additional Notes

  • State storage location: ~/.google_workspace_mcp/credentials/oauth_states.json
  • Respects GOOGLE_MCP_CREDENTIALS_DIR environment variable
  • Comprehensive logging for debugging OAuth flows
  • No breaking changes to existing functionality

…ount switching

- Create PersistentOAuthStateStore to persist OAuth states to disk
- Stores states in ~/.google_workspace_mcp/credentials/oauth_states.json
- Survives MCP server process restarts during OAuth flow
- Fixes 'Invalid or expired OAuth state parameter' error when switching
  between Claude Desktop accounts (Pro <-> Teams)
- Falls back to in-memory storage if persistent store fails
@taylorwilsdon
Copy link
Owner

I'd strongly encourage just running a single streamable HTTP instance and connecting via Claude Desktop's remote MCP connector rather than spawning a bunch of stdio instances per account. You don't need to deal with any of this if you do it that way and Claude will start up much faster too!

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.

2 participants