feat: add external OAuth 2.1 provider mode for bearer token authentication

Add support for external OAuth 2.1 provider mode where authentication
is handled by external systems that issue Google OAuth access tokens.

**Changes:**

1. **New Environment Variable: `EXTERNAL_OAUTH21_PROVIDER`**
   - Enables external OAuth mode when set to `true`
   - Requires `MCP_ENABLE_OAUTH21=true`
   - Disables protocol-level auth (MCP handshake/tools list work without auth)
   - Requires bearer tokens in Authorization headers for tool calls

2. **New File: `auth/external_oauth_provider.py`**
   - Custom provider extending FastMCP's GoogleProvider
   - Handles ya29.* Google OAuth access tokens
   - Validates tokens via google-auth library + userinfo API
   - Returns properly formatted AccessToken objects

3. **Modified: `auth/oauth_config.py`**
   - Add `external_oauth21_provider` config option
   - Validation that external mode requires OAuth 2.1
   - Helper methods for checking external provider mode

4. **Modified: `core/server.py`**
   - Use ExternalOAuthProvider when external mode enabled
   - Use standard GoogleProvider otherwise
   - Set server.auth = None for external mode (no protocol auth)

5. **Modified: `README.md`**
   - New "External OAuth 2.1 Provider Mode" section
   - Usage examples and configuration
   - Added to environment variables table

**How It Works:**
- MCP handshake and tools/list do NOT require authentication
- Tool calls require `Authorization: Bearer ya29.xxx` headers
- Tokens validated by calling Google's userinfo API
- Multi-user support via per-request authentication
- Stateless-compatible for containerized deployments

**Use Cases:**
- Integrating with existing authentication systems
- Custom OAuth flows managed by your application
- API gateways handling authentication upstream
- Multi-tenant SaaS with centralized auth
- Mobile/web apps with their own OAuth implementation

**Example Configuration:**
```bash
export MCP_ENABLE_OAUTH21=true
export EXTERNAL_OAUTH21_PROVIDER=true
export GOOGLE_OAUTH_CLIENT_ID=your_client_id
export GOOGLE_OAUTH_CLIENT_SECRET=your_client_secret
export WORKSPACE_MCP_STATELESS_MODE=true  # Optional
```

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Yair Weinberger
2025-10-24 15:43:29 +03:00
parent 95d5c8b4ad
commit 241f0987ae
4 changed files with 199 additions and 10 deletions

View File

@@ -95,16 +95,38 @@ def configure_server_for_http():
try:
required_scopes: List[str] = sorted(get_current_scopes())
provider = GoogleProvider(
client_id=config.client_id,
client_secret=config.client_secret,
base_url=config.get_oauth_base_url(),
redirect_path=config.redirect_path,
required_scopes=required_scopes,
)
server.auth = provider
# Check if external OAuth provider is configured
if config.is_external_oauth21_provider():
# External OAuth mode: use custom provider that handles ya29.* access tokens
from auth.external_oauth_provider import ExternalOAuthProvider
provider = ExternalOAuthProvider(
client_id=config.client_id,
client_secret=config.client_secret,
base_url=config.get_oauth_base_url(),
redirect_path=config.redirect_path,
required_scopes=required_scopes,
)
# Disable protocol-level auth, expect bearer tokens in tool calls
server.auth = None
logger.info("OAuth 2.1 enabled with EXTERNAL provider mode - protocol-level auth disabled")
logger.info("Expecting Authorization bearer tokens in tool call headers")
else:
# Standard OAuth 2.1 mode: use FastMCP's GoogleProvider
provider = GoogleProvider(
client_id=config.client_id,
client_secret=config.client_secret,
base_url=config.get_oauth_base_url(),
redirect_path=config.redirect_path,
required_scopes=required_scopes,
)
# Enable protocol-level auth
server.auth = provider
logger.info("OAuth 2.1 enabled using FastMCP GoogleProvider with protocol-level auth")
# Always set auth provider for token validation in middleware
set_auth_provider(provider)
logger.info("OAuth 2.1 enabled using FastMCP GoogleProvider")
_auth_provider = provider
except Exception as exc:
logger.error("Failed to initialize FastMCP GoogleProvider: %s", exc, exc_info=True)