diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..250863f
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,42 @@
+# Git and version control
+.git
+.gitignore
+gitdiff.txt
+
+# Documentation and notes
+*.md
+AUTHENTICATION_REFACTOR_PROPOSAL.md
+leverage_fastmcp_responses.md
+
+# Test files and coverage
+tests/
+htmlcov/
+.coverage
+pytest_out.txt
+
+# Build artifacts
+build/
+dist/
+*.egg-info/
+
+# Development files
+mcp_server_debug.log
+.credentials/
+
+# Cache and temporary files
+__pycache__/
+*.pyc
+*.pyo
+*.pyd
+.Python
+.pytest_cache/
+
+# IDE files
+.vscode/
+.idea/
+*.swp
+*.swo
+
+# OS files
+.DS_Store
+Thumbs.db
\ No newline at end of file
diff --git a/.env.oauth21 b/.env.oauth21
new file mode 100644
index 0000000..4f453cb
--- /dev/null
+++ b/.env.oauth21
@@ -0,0 +1,16 @@
+# OAuth 2.1 Configuration Example
+# Copy this to .env and update with your Google OAuth credentials
+
+# Required: Google OAuth 2.0 Client Credentials
+# Note: OAuth 2.1 will automatically use GOOGLE_OAUTH_CLIENT_ID and GOOGLE_OAUTH_CLIENT_SECRET
+# if OAUTH2_CLIENT_ID and OAUTH2_CLIENT_SECRET are not set
+
+GOOGLE_OAUTH_CLIENT_ID="your-google-client-id"
+GOOGLE_OAUTH_CLIENT_SECRET="your-google-client-secret"
+
+# Development Settings (set to true for localhost testing)
+OAUTH2_ALLOW_INSECURE_TRANSPORT=false
+OAUTH2_ENABLE_DEBUG=false
+
+# Legacy Compatibility (recommended during migration)
+OAUTH2_ENABLE_LEGACY_AUTH=true
diff --git a/README.md b/README.md
index 1f2fb07..ac9c56a 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,7 @@
 [![Website](https://img.shields.io/badge/Website-workspacemcp.com-green.svg)](https://workspacemcp.com)
 [![Verified on MseeP](https://mseep.ai/badge.svg)](https://mseep.ai/app/eebbc4a6-0f8c-41b2-ace8-038e5516dba0)
 
-**This is the single most feature-complete Google Workspace MCP server** now with 1-click Claude installation
+**The most feature-complete Google Workspace MCP server**, now with multi-user support and 1-click Claude installation
 
 *Full natural language control over Google Calendar, Drive, Gmail, Docs, Sheets, Slides, Forms, Tasks, and Chat through all MCP clients, AI assistants and developer tools.*
 
@@ -49,11 +49,11 @@
 
 ## Overview
 
-A production-ready MCP server that integrates all major Google Workspace services with AI assistants. Built with FastMCP for optimal performance, featuring advanced authentication handling, service caching, and streamlined development patterns.
+A production-ready MCP server that integrates all major Google Workspace services with AI assistants. It supports both single-user operation and multi-user authentication via OAuth 2.1, making it a powerful backend for custom applications. Built with FastMCP for optimal performance, featuring advanced authentication handling, service caching, and streamlined development patterns.
 
 ## Features
 
-- **🔐 Advanced OAuth 2.0**: Secure authentication with automatic token refresh, transport-aware callback handling, session management, and centralized scope management
+- **🔐 Advanced OAuth 2.0 & OAuth 2.1**: Secure authentication with automatic token refresh, transport-aware callback handling, session management, centralized scope management, and OAuth 2.1 bearer token support for multi-user environments with innovative CORS proxy architecture
 - **📅 Google Calendar**: Full calendar management with event CRUD operations
 - **📁 Google Drive**: File operations with native Microsoft Office format support (.docx, .xlsx)
 - **📧 Gmail**: Complete email management with search, send, and draft capabilities
@@ -89,11 +89,12 @@ A production-ready MCP server that integrates all major Google Workspace service
 
 | Variable | Purpose |
 |----------|---------|
-| `GOOGLE_OAUTH_CLIENT_ID` | OAuth client ID from Google Cloud |
-| `GOOGLE_OAUTH_CLIENT_SECRET` | OAuth client secret |
+| `GOOGLE_OAUTH_CLIENT_ID` | OAuth client ID from Google Cloud (used by both legacy auth and OAuth 2.1) |
+| `GOOGLE_OAUTH_CLIENT_SECRET` | OAuth client secret (used by both legacy auth and OAuth 2.1) |
 | `USER_GOOGLE_EMAIL` *(optional)* | Default email for single-user auth |
 | `GOOGLE_PSE_API_KEY` *(optional)* | API key for Google Custom Search - see [Custom Search Setup](#google-custom-search-setup) |
 | `GOOGLE_PSE_ENGINE_ID` *(optional)* | Programmable Search Engine ID for Custom Search |
+| `MCP_ENABLE_OAUTH21` *(optional)* | Set to `true` to enable OAuth 2.1 support (requires streamable-http transport) |
 | `OAUTHLIB_INSECURE_TRANSPORT=1` | Development only (allows `http://` redirect) |
 
 Claude Desktop stores these securely in the OS keychain; set them once in the extension pane.
@@ -155,10 +156,20 @@ Claude Desktop stores these securely in the OS keychain; set them once in the ex
      - Download credentials as `client_secret.json` in project root
      - To use a different location, set `GOOGLE_CLIENT_SECRET_PATH` (or legacy `GOOGLE_CLIENT_SECRETS`) environment variable with the file path
 
+     **Option C: .env File (Recommended for Development)**
+     - Copy the provided `.env.oauth21` example file to `.env` in the project root:
+     ```bash
+     cp .env.oauth21 .env
+     ```
+     - Edit the `.env` file to add your `GOOGLE_OAUTH_CLIENT_ID` and `GOOGLE_OAUTH_CLIENT_SECRET`.
+     - The server automatically loads variables from this file on startup, simplifying local development.
+
    **Credential Loading Priority**:
-   1. Environment variables (`GOOGLE_OAUTH_CLIENT_ID`, `GOOGLE_OAUTH_CLIENT_SECRET`)
-   2. File specified by `GOOGLE_CLIENT_SECRET_PATH` or `GOOGLE_CLIENT_SECRETS` environment variable
-   3. Default file (`client_secret.json` in project root)
+   The server loads credentials in the following order of precedence:
+   1. Manually set environment variables (e.g., `export VAR=value`).
+   2. Variables defined in a `.env` file in the project root.
+   3. `client_secret.json` file specified by `GOOGLE_CLIENT_SECRET_PATH`.
+   4. Default `client_secret.json` file in the project root.
 
    **Why Environment Variables?**
    - ✅ Containerized deployments (Docker, Kubernetes)
@@ -228,6 +239,45 @@ docker run -p 8000:8000 -v $(pwd):/app workspace-mcp --transport streamable-http
 
 **Available Tools for `--tools` flag**: `gmail`, `drive`, `calendar`, `docs`, `sheets`, `forms`, `tasks`, `chat`, `search`
 
+### OAuth 2.1 Support (Multi-User Bearer Token Authentication)
+
+The server includes OAuth 2.1 support for bearer token authentication, enabling multi-user session management. **OAuth 2.1 automatically reuses your existing `GOOGLE_OAUTH_CLIENT_ID` and `GOOGLE_OAUTH_CLIENT_SECRET` credentials** - no additional configuration needed!
+
+**When to use OAuth 2.1:**
+- Multiple users accessing the same MCP server instance
+- Need for bearer token authentication instead of passing user emails
+- Building web applications or APIs on top of the MCP server
+- Production environments requiring secure session management
+- Browser-based clients requiring CORS support
+
+**Enabling OAuth 2.1:**
+To enable OAuth 2.1, set the `MCP_ENABLE_OAUTH21` environment variable to `true`.
+
+```bash
+# OAuth 2.1 requires HTTP transport mode
+export MCP_ENABLE_OAUTH21=true
+uv run main.py --transport streamable-http
+```
+
+If `MCP_ENABLE_OAUTH21` is not set to `true`, the server will use legacy authentication, which is suitable for clients that do not support OAuth 2.1.
+
+<details>
+<summary><b>Innovative CORS Proxy Architecture</b></summary>
+
+This implementation solves two critical challenges when using Google OAuth in browser environments:
+
+1.  **Dynamic Client Registration**: Google doesn't support OAuth 2.1 dynamic client registration. Our server provides a clever proxy that accepts any client registration request and returns the pre-configured Google OAuth credentials, allowing standards-compliant clients to work seamlessly.
+
+2.  **CORS Issues**: Google's OAuth endpoints don't include CORS headers, blocking browser-based clients. We implement intelligent proxy endpoints that:
+   - Proxy authorization server discovery requests through `/auth/discovery/authorization-server/{server}`
+   - Proxy token exchange requests through `/oauth2/token`
+   - Add proper CORS headers to all responses
+   - Maintain security by only proxying to known Google OAuth endpoints
+
+This architecture enables any OAuth 2.1 compliant client to authenticate users through Google, even from browser environments, without requiring changes to the client implementation.
+
+</details>
+
 ### Connect to Claude Desktop
 
 The server supports two transport modes:
diff --git a/auth/auth_info_middleware.py b/auth/auth_info_middleware.py
new file mode 100644
index 0000000..97d6ddd
--- /dev/null
+++ b/auth/auth_info_middleware.py
@@ -0,0 +1,276 @@
+"""
+Authentication middleware to populate context state with user information
+"""
+import jwt
+import logging
+import os
+import time
+from types import SimpleNamespace
+from fastmcp.server.middleware import Middleware, MiddlewareContext
+from fastmcp.server.dependencies import get_http_headers
+
+# Configure logging
+logger = logging.getLogger(__name__)
+
+
+class AuthInfoMiddleware(Middleware):
+    """
+    Middleware to extract authentication information from JWT tokens
+    and populate the FastMCP context state for use in tools and prompts.
+    """
+    
+    def __init__(self):
+        super().__init__()
+        self.auth_provider_type = "Unknown"
+    
+    async def _process_request_for_auth(self, context: MiddlewareContext):
+        """Helper to extract, verify, and store auth info from a request."""
+        if not context.fastmcp_context:
+            logger.warning("No fastmcp_context available")
+            return
+
+        # Return early if authentication state is already set
+        if context.fastmcp_context.get_state("authenticated_user_email"):
+            logger.info("Authentication state already set.")
+            return
+
+        # Try to get the HTTP request to extract Authorization header
+        try:
+            # Use the new FastMCP method to get HTTP headers
+            headers = get_http_headers()
+            if headers:
+                logger.info(f"Got HTTP headers: {type(headers)}")
+                
+                # Get the Authorization header
+                auth_header = headers.get("authorization", "")
+                if auth_header.startswith("Bearer "):
+                    token_str = auth_header[7:]  # Remove "Bearer " prefix
+                    logger.info("Found Bearer token in HTTP request")
+                    
+                    # For Google OAuth tokens (ya29.*), we need to verify them differently
+                    if token_str.startswith("ya29."):
+                        logger.info("Detected Google OAuth access token")
+                        
+                        # Verify the token to get user info
+                        from core.server import get_auth_provider
+                        auth_provider = get_auth_provider()
+                        
+                        if auth_provider:
+                            try:
+                                # Verify the token
+                                verified_auth = await auth_provider.verify_token(token_str)
+                                if verified_auth:
+                                    # Extract user info from verified token
+                                    user_email = None
+                                    if hasattr(verified_auth, 'claims'):
+                                        user_email = verified_auth.claims.get("email")
+                                    
+                                    # Get expires_at, defaulting to 1 hour from now if not available
+                                    if hasattr(verified_auth, 'expires_at'):
+                                        expires_at = verified_auth.expires_at
+                                    else:
+                                        expires_at = int(time.time()) + 3600  # Default to 1 hour
+                                    
+                                    # Get client_id from verified auth or use default
+                                    client_id = getattr(verified_auth, 'client_id', None) or "google"
+                                    
+                                    access_token = SimpleNamespace(
+                                        token=token_str,
+                                        client_id=client_id,
+                                        scopes=verified_auth.scopes if hasattr(verified_auth, 'scopes') else [],
+                                        session_id=f"google_oauth_{token_str[:8]}",
+                                        expires_at=expires_at,
+                                        # Add other fields that might be needed
+                                        sub=verified_auth.sub if hasattr(verified_auth, 'sub') else user_email,
+                                        email=user_email
+                                    )
+                                    
+                                    # Store in context state - this is the authoritative authentication state
+                                    context.fastmcp_context.set_state("access_token", access_token)
+                                    context.fastmcp_context.set_state("auth_provider_type", self.auth_provider_type)
+                                    context.fastmcp_context.set_state("token_type", "google_oauth")
+                                    context.fastmcp_context.set_state("user_email", user_email)
+                                    context.fastmcp_context.set_state("username", user_email)
+                                    # Set the definitive authentication state
+                                    context.fastmcp_context.set_state("authenticated_user_email", user_email)
+                                    context.fastmcp_context.set_state("authenticated_via", "bearer_token")
+                                    
+                                    logger.info(f"Stored verified Google OAuth token for user: {user_email}")
+                                else:
+                                    logger.error("Failed to verify Google OAuth token")
+                                # Don't set authenticated_user_email if verification failed
+                            except Exception as e:
+                                logger.error(f"Error verifying Google OAuth token: {e}")
+                                # Still store the unverified token - service decorator will handle verification
+                                access_token = SimpleNamespace(
+                                    token=token_str,
+                                    client_id=os.getenv("GOOGLE_OAUTH_CLIENT_ID", "google"),
+                                    scopes=[],
+                                    session_id=f"google_oauth_{token_str[:8]}",
+                                    expires_at=int(time.time()) + 3600,  # Default to 1 hour
+                                    sub="unknown",
+                                    email=""
+                                )
+                                context.fastmcp_context.set_state("access_token", access_token)
+                                context.fastmcp_context.set_state("auth_provider_type", self.auth_provider_type)
+                                context.fastmcp_context.set_state("token_type", "google_oauth")
+                        else:
+                            logger.warning("No auth provider available to verify Google token")
+                            # Store unverified token
+                            access_token = SimpleNamespace(
+                                token=token_str,
+                                client_id=os.getenv("GOOGLE_OAUTH_CLIENT_ID", "google"),
+                                scopes=[],
+                                session_id=f"google_oauth_{token_str[:8]}",
+                                expires_at=int(time.time()) + 3600,  # Default to 1 hour
+                                sub="unknown",
+                                email=""
+                            )
+                            context.fastmcp_context.set_state("access_token", access_token)
+                            context.fastmcp_context.set_state("auth_provider_type", self.auth_provider_type)
+                            context.fastmcp_context.set_state("token_type", "google_oauth")
+                        
+                    else:
+                        # Decode JWT to get user info
+                        try:
+                            token_payload = jwt.decode(
+                                token_str,
+                                options={"verify_signature": False}
+                            )
+                            logger.debug(f"JWT payload decoded: {list(token_payload.keys())}")
+                            
+                            # Create an AccessToken-like object
+                            access_token = SimpleNamespace(
+                                token=token_str,
+                                client_id=token_payload.get("client_id", "unknown"),
+                                scopes=token_payload.get("scope", "").split() if token_payload.get("scope") else [],
+                                session_id=token_payload.get("sid", token_payload.get("jti", token_payload.get("session_id", "unknown"))),
+                                expires_at=token_payload.get("exp", 0)
+                            )
+                            
+                            # Store in context state
+                            context.fastmcp_context.set_state("access_token", access_token)
+                            
+                            # Store additional user info
+                            context.fastmcp_context.set_state("user_id", token_payload.get("sub"))
+                            context.fastmcp_context.set_state("username", token_payload.get("username", token_payload.get("email")))
+                            context.fastmcp_context.set_state("name", token_payload.get("name"))
+                            context.fastmcp_context.set_state("auth_time", token_payload.get("auth_time"))
+                            context.fastmcp_context.set_state("issuer", token_payload.get("iss"))
+                            context.fastmcp_context.set_state("audience", token_payload.get("aud"))
+                            context.fastmcp_context.set_state("jti", token_payload.get("jti"))
+                            context.fastmcp_context.set_state("auth_provider_type", self.auth_provider_type)
+                            
+                            # Set the definitive authentication state for JWT tokens
+                            user_email = token_payload.get("email", token_payload.get("username"))
+                            if user_email:
+                                context.fastmcp_context.set_state("authenticated_user_email", user_email)
+                                context.fastmcp_context.set_state("authenticated_via", "jwt_token")
+                            
+                            logger.info("Successfully extracted and stored auth info from HTTP request")
+                            
+                        except jwt.DecodeError as e:
+                            logger.error(f"Failed to decode JWT: {e}")
+                        except Exception as e:
+                            logger.error(f"Error processing JWT: {e}")
+                else:
+                    logger.debug("No Bearer token in Authorization header")
+            else:
+                logger.debug("No HTTP headers available (might be using stdio transport)")
+        except Exception as e:
+            logger.debug(f"Could not get HTTP request: {e}")
+        
+        # After trying HTTP headers, check for other authentication methods
+        # This consolidates all authentication logic in the middleware
+        if not context.fastmcp_context.get_state("authenticated_user_email"):
+            logger.debug("No authentication found via bearer token, checking other methods")
+            
+            # Check transport mode
+            from core.config import get_transport_mode
+            transport_mode = get_transport_mode()
+            
+            if transport_mode == "stdio":
+                # In stdio mode, check if there's a session with credentials
+                # This is ONLY safe in stdio mode because it's single-user
+                logger.debug("Checking for stdio mode authentication")
+                
+                # Get the requested user from the context if available
+                requested_user = None
+                if hasattr(context, 'request') and hasattr(context.request, 'params'):
+                    requested_user = context.request.params.get('user_google_email')
+                elif hasattr(context, 'arguments'):
+                    # FastMCP may store arguments differently
+                    requested_user = context.arguments.get('user_google_email')
+                
+                if requested_user:
+                    try:
+                        from auth.oauth21_session_store import get_oauth21_session_store
+                        store = get_oauth21_session_store()
+                        
+                        # Check if user has a recent session
+                        if store.has_session(requested_user):
+                            logger.info(f"User {requested_user} has recent auth session in stdio mode")
+                            # In stdio mode, we can trust the user has authenticated recently
+                            context.fastmcp_context.set_state("authenticated_user_email", requested_user)
+                            context.fastmcp_context.set_state("authenticated_via", "stdio_session")
+                            context.fastmcp_context.set_state("auth_provider_type", "oauth21_stdio")
+                    except Exception as e:
+                        logger.debug(f"Error checking stdio session: {e}")
+            
+            # Check for MCP session binding
+            if not context.fastmcp_context.get_state("authenticated_user_email") and hasattr(context.fastmcp_context, 'session_id'):
+                mcp_session_id = context.fastmcp_context.session_id
+                if mcp_session_id:
+                    try:
+                        from auth.oauth21_session_store import get_oauth21_session_store
+                        store = get_oauth21_session_store()
+                        
+                        # Check if this MCP session is bound to a user
+                        bound_user = store.get_user_by_mcp_session(mcp_session_id)
+                        if bound_user:
+                            logger.info(f"MCP session {mcp_session_id} is bound to user {bound_user}")
+                            context.fastmcp_context.set_state("authenticated_user_email", bound_user)
+                            context.fastmcp_context.set_state("authenticated_via", "mcp_session_binding")
+                            context.fastmcp_context.set_state("auth_provider_type", "oauth21_session")
+                    except Exception as e:
+                        logger.debug(f"Error checking MCP session binding: {e}")
+    
+    async def on_call_tool(self, context: MiddlewareContext, call_next):
+        """Extract auth info from token and set in context state"""
+        logger.info("on_call_tool called")
+        
+        try:
+            await self._process_request_for_auth(context)
+            
+            logger.info("Calling next middleware/handler")
+            result = await call_next(context)
+            logger.info("Successfully completed call_next()")
+            return result
+            
+        except Exception as e:
+            # Check if this is an authentication error - don't log traceback for these
+            if "GoogleAuthenticationError" in str(type(e)) or "Access denied: Cannot retrieve credentials" in str(e):
+                logger.info(f"Authentication check failed: {e}")
+            else:
+                logger.error(f"Error in on_call_tool middleware: {e}", exc_info=True)
+            raise
+    
+    async def on_get_prompt(self, context: MiddlewareContext, call_next):
+        """Extract auth info for prompt requests too"""
+        logger.info("on_get_prompt called")
+        
+        try:
+            await self._process_request_for_auth(context)
+            
+            logger.info("Calling next middleware/handler for prompt")
+            result = await call_next(context)
+            logger.info("Successfully completed prompt call_next()")
+            return result
+            
+        except Exception as e:
+            # Check if this is an authentication error - don't log traceback for these
+            if "GoogleAuthenticationError" in str(type(e)) or "Access denied: Cannot retrieve credentials" in str(e):
+                logger.info(f"Authentication check failed in prompt: {e}")
+            else:
+                logger.error(f"Error in on_get_prompt middleware: {e}", exc_info=True)
+            raise
\ No newline at end of file
diff --git a/auth/fastmcp_google_auth.py b/auth/fastmcp_google_auth.py
new file mode 100644
index 0000000..b43bd08
--- /dev/null
+++ b/auth/fastmcp_google_auth.py
@@ -0,0 +1,170 @@
+"""
+Google Workspace Authentication Provider for FastMCP
+
+This module implements OAuth 2.1 authentication for Google Workspace using FastMCP's
+built-in authentication patterns. It acts as a Resource Server (RS) that trusts
+Google as the Authorization Server (AS).
+
+Key features:
+- JWT token verification using Google's public keys
+- Discovery metadata endpoints for MCP protocol compliance
+- CORS proxy endpoints to work around Google's CORS limitations
+- Session bridging to Google credentials for API access
+"""
+
+import os
+import logging
+from typing import Dict, Any, Optional, List
+
+from starlette.routing import Route
+
+from fastmcp.server.auth.auth import AuthProvider
+from fastmcp.server.auth.providers.jwt import JWTVerifier
+from mcp.server.auth.provider import AccessToken
+
+logger = logging.getLogger(__name__)
+
+
+class GoogleWorkspaceAuthProvider(AuthProvider):
+    """
+    Authentication provider for Google Workspace integration.
+    
+    This provider implements the Remote Authentication pattern where:
+    - Google acts as the Authorization Server (AS)
+    - This MCP server acts as a Resource Server (RS)
+    - Tokens are verified using Google's public keys
+    """
+    
+    def __init__(self):
+        """Initialize the Google Workspace auth provider."""
+        super().__init__()
+        
+        # Get configuration from environment
+        self.client_id = os.getenv("GOOGLE_OAUTH_CLIENT_ID")
+        self.client_secret = os.getenv("GOOGLE_OAUTH_CLIENT_SECRET")
+        self.base_url = os.getenv("WORKSPACE_MCP_BASE_URI", "http://localhost")
+        self.port = int(os.getenv("PORT", os.getenv("WORKSPACE_MCP_PORT", 8000)))
+        
+        if not self.client_id:
+            logger.warning("GOOGLE_OAUTH_CLIENT_ID not set - OAuth 2.1 authentication will not work")
+            return
+            
+        # Initialize JWT verifier for Google tokens
+        self.jwt_verifier = JWTVerifier(
+            jwks_uri="https://www.googleapis.com/oauth2/v3/certs",
+            issuer="https://accounts.google.com",
+            audience=self.client_id,
+            algorithm="RS256"
+        )
+        
+        # Session bridging now handled by OAuth21SessionStore
+        
+    async def verify_token(self, token: str) -> Optional[AccessToken]:
+        """
+        Verify a bearer token issued by Google.
+        
+        Args:
+            token: The bearer token to verify
+            
+        Returns:
+            AccessToken object if valid, None otherwise
+        """
+        if not self.client_id:
+            return None
+            
+        try:
+            # Use FastMCP's JWT verifier
+            access_token = await self.jwt_verifier.verify_token(token)
+            
+            if access_token:
+                # Store session info in OAuth21SessionStore for credential bridging
+                user_email = access_token.claims.get("email")
+                if user_email:
+                    from auth.oauth21_session_store import get_oauth21_session_store
+                    store = get_oauth21_session_store()
+                    session_id = f"google_{access_token.claims.get('sub', 'unknown')}"
+                    
+                    # Try to get FastMCP session ID for binding
+                    mcp_session_id = None
+                    try:
+                        from fastmcp.server.dependencies import get_context
+                        ctx = get_context()
+                        if ctx and hasattr(ctx, 'session_id'):
+                            mcp_session_id = ctx.session_id
+                            logger.debug(f"Binding MCP session {mcp_session_id} to user {user_email}")
+                    except Exception:
+                        pass
+                    
+                    store.store_session(
+                        user_email=user_email,
+                        access_token=token,
+                        scopes=access_token.scopes or [],
+                        session_id=session_id,
+                        mcp_session_id=mcp_session_id
+                    )
+                
+                logger.debug(f"Successfully verified Google token for user: {user_email}")
+                
+            return access_token
+            
+        except Exception as e:
+            logger.error(f"Failed to verify Google token: {e}")
+            return None
+    
+    def customize_auth_routes(self, routes: List[Route]) -> List[Route]:
+        """
+        NOTE: This method is not currently used. All OAuth 2.1 routes are implemented 
+        directly in core/server.py using @server.custom_route decorators.
+        
+        This method exists for compatibility with FastMCP's AuthProvider interface
+        but the routes it would define are handled elsewhere.
+        """
+        # Routes are implemented directly in core/server.py
+        return routes
+    
+    def get_session_info(self, session_id: str) -> Optional[Dict[str, Any]]:
+        """
+        Get session information for credential bridging from OAuth21SessionStore.
+        
+        Args:
+            session_id: The session identifier
+            
+        Returns:
+            Session information if found
+        """
+        from auth.oauth21_session_store import get_oauth21_session_store
+        store = get_oauth21_session_store()
+        
+        # Try to get user by session_id (assuming it's the MCP session ID)
+        user_email = store.get_user_by_mcp_session(session_id)
+        if user_email:
+            credentials = store.get_credentials(user_email)
+            if credentials:
+                return {
+                    "access_token": credentials.token,
+                    "user_email": user_email,
+                    "scopes": credentials.scopes or []
+                }
+        return None
+    
+    def create_session_from_token(self, token: str, user_email: str) -> str:
+        """
+        Create a session from an access token for credential bridging using OAuth21SessionStore.
+        
+        Args:
+            token: The access token
+            user_email: The user's email address
+            
+        Returns:
+            Session ID
+        """
+        from auth.oauth21_session_store import get_oauth21_session_store
+        store = get_oauth21_session_store()
+        session_id = f"google_{user_email}"
+        
+        store.store_session(
+            user_email=user_email,
+            access_token=token,
+            session_id=session_id
+        )
+        return session_id
\ No newline at end of file
diff --git a/auth/google_auth.py b/auth/google_auth.py
index c278ceb..f52abc6 100644
--- a/auth/google_auth.py
+++ b/auth/google_auth.py
@@ -16,6 +16,20 @@ from google.auth.exceptions import RefreshError
 from googleapiclient.discovery import build
 from googleapiclient.errors import HttpError
 from auth.scopes import SCOPES
+from auth.oauth21_session_store import get_oauth21_session_store
+from core.config import (
+    WORKSPACE_MCP_PORT,
+    WORKSPACE_MCP_BASE_URI,
+    get_transport_mode,
+    get_oauth_redirect_uri,
+)
+from core.context import get_fastmcp_session_id
+
+# Try to import FastMCP dependencies (may not be available in all environments)
+try:
+    from fastmcp.server.dependencies import get_context as get_fastmcp_context
+except ImportError:
+    get_fastmcp_context = None
 
 # Configure logging
 logging.basicConfig(level=logging.INFO)
@@ -40,10 +54,7 @@ def get_default_credentials_dir():
 
 DEFAULT_CREDENTIALS_DIR = get_default_credentials_dir()
 
-# In-memory cache for session credentials, maps session_id to Credentials object
-# This is brittle and bad, but our options are limited with Claude in present state.
-# This should be more robust in a production system once OAuth2.1 is implemented in client.
-_SESSION_CREDENTIALS_CACHE: Dict[str, Credentials] = {}
+# Session credentials now handled by OAuth21SessionStore - no local cache needed
 # Centralized Client Secrets Path Logic
 _client_secrets_env = os.getenv("GOOGLE_CLIENT_SECRET_PATH") or os.getenv(
     "GOOGLE_CLIENT_SECRETS"
@@ -139,9 +150,34 @@ def save_credentials_to_file(
 
 
 def save_credentials_to_session(session_id: str, credentials: Credentials):
-    """Saves user credentials to the in-memory session cache."""
-    _SESSION_CREDENTIALS_CACHE[session_id] = credentials
-    logger.debug(f"Credentials saved to session cache for session_id: {session_id}")
+    """Saves user credentials using OAuth21SessionStore."""
+    # Get user email from credentials if possible
+    user_email = None
+    if credentials and credentials.id_token:
+        try:
+            decoded_token = jwt.decode(
+                credentials.id_token, options={"verify_signature": False}
+            )
+            user_email = decoded_token.get("email")
+        except Exception as e:
+            logger.debug(f"Could not decode id_token to get email: {e}")
+    
+    if user_email:
+        store = get_oauth21_session_store()
+        store.store_session(
+            user_email=user_email,
+            access_token=credentials.token,
+            refresh_token=credentials.refresh_token,
+            token_uri=credentials.token_uri,
+            client_id=credentials.client_id,
+            client_secret=credentials.client_secret,
+            scopes=credentials.scopes,
+            expiry=credentials.expiry,
+            mcp_session_id=session_id
+        )
+        logger.debug(f"Credentials saved to OAuth21SessionStore for session_id: {session_id}, user: {user_email}")
+    else:
+        logger.warning(f"Could not save credentials to session store - no user email found for session: {session_id}")
 
 
 def load_credentials_from_file(
@@ -164,6 +200,9 @@ def load_credentials_from_file(
         if creds_data.get("expiry"):
             try:
                 expiry = datetime.fromisoformat(creds_data["expiry"])
+                # Ensure timezone-naive datetime for Google auth library compatibility
+                if expiry.tzinfo is not None:
+                    expiry = expiry.replace(tzinfo=None)
             except (ValueError, TypeError) as e:
                 logger.warning(
                     f"Could not parse expiry time for {user_google_email}: {e}"
@@ -190,15 +229,16 @@ def load_credentials_from_file(
 
 
 def load_credentials_from_session(session_id: str) -> Optional[Credentials]:
-    """Loads user credentials from the in-memory session cache."""
-    credentials = _SESSION_CREDENTIALS_CACHE.get(session_id)
+    """Loads user credentials from OAuth21SessionStore."""
+    store = get_oauth21_session_store()
+    credentials = store.get_credentials_by_mcp_session(session_id)
     if credentials:
         logger.debug(
-            f"Credentials loaded from session cache for session_id: {session_id}"
+            f"Credentials loaded from OAuth21SessionStore for session_id: {session_id}"
         )
     else:
         logger.debug(
-            f"No credentials found in session cache for session_id: {session_id}"
+            f"No credentials found in OAuth21SessionStore for session_id: {session_id}"
         )
     return credentials
 
@@ -379,15 +419,7 @@ async def start_auth_flow(
         f"[start_auth_flow] Initiating auth for {user_display_name} with global SCOPES."
     )
 
-    # Import here to avoid circular imports
-    from auth.oauth_callback_server import ensure_oauth_callback_available
-    from core.server import _current_transport_mode, WORKSPACE_MCP_PORT, WORKSPACE_MCP_BASE_URI
-
-    # Ensure OAuth callback server is available before generating URLs
-    success, error_msg = ensure_oauth_callback_available(_current_transport_mode, WORKSPACE_MCP_PORT, WORKSPACE_MCP_BASE_URI)
-    if not success:
-        error_detail = f" ({error_msg})" if error_msg else ""
-        raise Exception(f"Cannot initiate OAuth flow - callback server unavailable{error_detail}. Please ensure the OAuth callback server can start before attempting authentication.")
+    # Note: Caller should ensure OAuth callback is available before calling this function
 
     try:
         if "OAUTHLIB_INSECURE_TRANSPORT" not in os.environ and (
@@ -518,7 +550,22 @@ def handle_auth_callback(
         # Save the credentials to file
         save_credentials_to_file(user_google_email, credentials, credentials_base_dir)
 
-        # If session_id is provided, also save to session cache
+        # Always save to OAuth21SessionStore for centralized management
+        store = get_oauth21_session_store()
+        store.store_session(
+            user_email=user_google_email,
+            access_token=credentials.token,
+            refresh_token=credentials.refresh_token,
+            token_uri=credentials.token_uri,
+            client_id=credentials.client_id,
+            client_secret=credentials.client_secret,
+            scopes=credentials.scopes,
+            expiry=credentials.expiry,
+            mcp_session_id=session_id,
+            issuer="https://accounts.google.com"  # Add issuer for Google tokens
+        )
+
+        # If session_id is provided, also save to session cache for compatibility
         if session_id:
             save_credentials_to_session(session_id, credentials)
 
@@ -537,7 +584,7 @@ def get_credentials(
     session_id: Optional[str] = None,
 ) -> Optional[Credentials]:
     """
-    Retrieves stored credentials, prioritizing session, then file. Refreshes if necessary.
+    Retrieves stored credentials, prioritizing OAuth 2.1 store, then session, then file. Refreshes if necessary.
     If credentials are loaded from file and a session_id is present, they are cached in the session.
     In single-user mode, bypasses session mapping and uses any available credentials.
 
@@ -551,6 +598,51 @@ def get_credentials(
     Returns:
         Valid Credentials object or None.
     """
+    # First, try OAuth 2.1 session store if we have a session_id (FastMCP session)
+    if session_id:
+        try:
+            store = get_oauth21_session_store()
+
+            # Try to get credentials by MCP session
+            credentials = store.get_credentials_by_mcp_session(session_id)
+            if credentials:
+                logger.info(f"[get_credentials] Found OAuth 2.1 credentials for MCP session {session_id}")
+
+                # Check scopes
+                if not all(scope in credentials.scopes for scope in required_scopes):
+                    logger.warning(
+                        f"[get_credentials] OAuth 2.1 credentials lack required scopes. Need: {required_scopes}, Have: {credentials.scopes}"
+                    )
+                    return None
+
+                # Return if valid
+                if credentials.valid:
+                    return credentials
+                elif credentials.expired and credentials.refresh_token:
+                    # Try to refresh
+                    try:
+                        credentials.refresh(Request())
+                        logger.info(f"[get_credentials] Refreshed OAuth 2.1 credentials for session {session_id}")
+                        # Update stored credentials
+                        user_email = store.get_user_by_mcp_session(session_id)
+                        if user_email:
+                            store.store_session(
+                                user_email=user_email,
+                                access_token=credentials.token,
+                                refresh_token=credentials.refresh_token,
+                                scopes=credentials.scopes,
+                                expiry=credentials.expiry,
+                                mcp_session_id=session_id
+                            )
+                        return credentials
+                    except Exception as e:
+                        logger.error(f"[get_credentials] Failed to refresh OAuth 2.1 credentials: {e}")
+                        return None
+        except ImportError:
+            pass  # OAuth 2.1 store not available
+        except Exception as e:
+            logger.debug(f"[get_credentials] Error checking OAuth 2.1 store: {e}")
+
     # Check for single-user mode
     if os.getenv("MCP_SINGLE_USER_MODE") == "1":
         logger.info(
@@ -659,6 +751,22 @@ def get_credentials(
                 save_credentials_to_file(
                     user_google_email, credentials, credentials_base_dir
                 )
+                
+                # Also update OAuth21SessionStore
+                store = get_oauth21_session_store()
+                store.store_session(
+                    user_email=user_google_email,
+                    access_token=credentials.token,
+                    refresh_token=credentials.refresh_token,
+                    token_uri=credentials.token_uri,
+                    client_id=credentials.client_id,
+                    client_secret=credentials.client_secret,
+                    scopes=credentials.scopes,
+                    expiry=credentials.expiry,
+                    mcp_session_id=session_id,
+                    issuer="https://accounts.google.com"  # Add issuer for Google tokens
+                )
+                
             if session_id:  # Update session cache if it was the source or is active
                 save_credentials_to_session(session_id, credentials)
             return credentials
@@ -719,6 +827,7 @@ async def get_authenticated_google_service(
     tool_name: str,  # For logging/debugging
     user_google_email: str,  # Required - no more Optional
     required_scopes: List[str],
+    session_id: Optional[str] = None,  # Session context for logging
 ) -> tuple[Any, str]:
     """
     Centralized Google service authentication for all MCP tools.
@@ -737,8 +846,37 @@ async def get_authenticated_google_service(
     Raises:
         GoogleAuthenticationError: When authentication is required or fails
     """
+
+    # Try to get FastMCP session ID if not provided
+    if not session_id:
+        try:
+            # First try context variable (works in async context)
+            session_id = get_fastmcp_session_id()
+            if session_id:
+                logger.debug(f"[{tool_name}] Got FastMCP session ID from context: {session_id}")
+            else:
+                logger.debug(f"[{tool_name}] Context variable returned None/empty session ID")
+        except Exception as e:
+            logger.debug(f"[{tool_name}] Could not get FastMCP session from context: {e}")
+
+        # Fallback to direct FastMCP context if context variable not set
+        if not session_id and get_fastmcp_context:
+            try:
+                fastmcp_ctx = get_fastmcp_context()
+                if fastmcp_ctx and hasattr(fastmcp_ctx, 'session_id'):
+                    session_id = fastmcp_ctx.session_id
+                    logger.debug(f"[{tool_name}] Got FastMCP session ID directly: {session_id}")
+                else:
+                    logger.debug(f"[{tool_name}] FastMCP context exists but no session_id attribute")
+            except Exception as e:
+                logger.debug(f"[{tool_name}] Could not get FastMCP context directly: {e}")
+
+        # Final fallback: log if we still don't have session_id
+        if not session_id:
+            logger.warning(f"[{tool_name}] Unable to obtain FastMCP session ID from any source")
+
     logger.info(
-        f"[{tool_name}] Attempting to get authenticated {service_name} service. Email: '{user_google_email}'"
+        f"[{tool_name}] Attempting to get authenticated {service_name} service. Email: '{user_google_email}', Session: '{session_id}'"
     )
 
     # Validate email format
@@ -752,7 +890,7 @@ async def get_authenticated_google_service(
         user_google_email=user_google_email,
         required_scopes=required_scopes,
         client_secrets_path=CONFIG_CLIENT_SECRETS_PATH,
-        session_id=None,  # Session ID not available in service layer
+        session_id=session_id,  # Pass through session context
     )
 
     if not credentials or not credentials.valid:
@@ -763,12 +901,13 @@ async def get_authenticated_google_service(
             f"[{tool_name}] Valid email '{user_google_email}' provided, initiating auth flow."
         )
 
-        # Import here to avoid circular import
-        from core.server import get_oauth_redirect_uri_for_current_mode
-
         # Ensure OAuth callback is available
-        redirect_uri = get_oauth_redirect_uri_for_current_mode()
-        # Note: We don't know the transport mode here, but the server should have set it
+        from auth.oauth_callback_server import ensure_oauth_callback_available
+        redirect_uri = get_oauth_redirect_uri()
+        success, error_msg = ensure_oauth_callback_available(get_transport_mode(), WORKSPACE_MCP_PORT, WORKSPACE_MCP_BASE_URI)
+        if not success:
+            error_detail = f" ({error_msg})" if error_msg else ""
+            raise GoogleAuthenticationError(f"Cannot initiate OAuth flow - callback server unavailable{error_detail}")
 
         # Generate auth URL and raise exception with it
         auth_response = await start_auth_flow(
diff --git a/auth/google_remote_auth_provider.py b/auth/google_remote_auth_provider.py
new file mode 100644
index 0000000..1b0e924
--- /dev/null
+++ b/auth/google_remote_auth_provider.py
@@ -0,0 +1,233 @@
+"""
+Google Workspace RemoteAuthProvider for FastMCP v2.11.1+
+
+This module implements OAuth 2.1 authentication for Google Workspace using FastMCP's
+RemoteAuthProvider pattern. It provides:
+
+- JWT token verification using Google's public keys
+- OAuth proxy endpoints to work around CORS restrictions
+- Dynamic client registration workaround
+- Session bridging to Google credentials for API access
+
+This provider is used only in streamable-http transport mode with FastMCP v2.11.1+.
+For earlier versions or other transport modes, the legacy GoogleWorkspaceAuthProvider is used.
+"""
+
+import os
+import logging
+import aiohttp
+from typing import Optional, List
+
+from starlette.routing import Route
+from pydantic import AnyHttpUrl
+
+try:
+    from fastmcp.server.auth import RemoteAuthProvider
+    from fastmcp.server.auth.providers.jwt import JWTVerifier
+    REMOTEAUTHPROVIDER_AVAILABLE = True
+except ImportError:
+    REMOTEAUTHPROVIDER_AVAILABLE = False
+    RemoteAuthProvider = object  # Fallback for type hints
+    JWTVerifier = object
+
+
+# Import common OAuth handlers
+from auth.oauth_common_handlers import (
+    handle_oauth_authorize,
+    handle_proxy_token_exchange,
+    handle_oauth_authorization_server,
+    handle_oauth_client_config,
+    handle_oauth_register
+)
+
+logger = logging.getLogger(__name__)
+
+
+class GoogleRemoteAuthProvider(RemoteAuthProvider):
+    """
+    RemoteAuthProvider implementation for Google Workspace using FastMCP v2.11.1+.
+    
+    This provider extends RemoteAuthProvider to add:
+    - OAuth proxy endpoints for CORS workaround
+    - Dynamic client registration support
+    - Enhanced session management with issuer tracking
+    """
+    
+    def __init__(self):
+        """Initialize the Google RemoteAuthProvider."""
+        if not REMOTEAUTHPROVIDER_AVAILABLE:
+            raise ImportError("FastMCP v2.11.1+ required for RemoteAuthProvider")
+        
+        # Get configuration from environment
+        self.client_id = os.getenv("GOOGLE_OAUTH_CLIENT_ID")
+        self.client_secret = os.getenv("GOOGLE_OAUTH_CLIENT_SECRET")
+        self.base_url = os.getenv("WORKSPACE_MCP_BASE_URI", "http://localhost")
+        self.port = int(os.getenv("PORT", os.getenv("WORKSPACE_MCP_PORT", 8000)))
+        
+        if not self.client_id:
+            logger.warning("GOOGLE_OAUTH_CLIENT_ID not set - OAuth 2.1 authentication will not work")
+            # Still initialize to avoid errors, but auth won't work
+        
+        # Configure JWT verifier for Google tokens
+        token_verifier = JWTVerifier(
+            jwks_uri="https://www.googleapis.com/oauth2/v3/certs",
+            issuer="https://accounts.google.com",
+            audience=self.client_id or "placeholder",  # Use placeholder if not configured
+            algorithm="RS256"
+        )
+        
+        # Initialize RemoteAuthProvider with local server as the authorization server
+        # This ensures OAuth discovery points to our proxy endpoints instead of Google directly
+        super().__init__(
+            token_verifier=token_verifier,
+            authorization_servers=[AnyHttpUrl(f"{self.base_url}:{self.port}")],
+            resource_server_url=f"{self.base_url}:{self.port}"
+        )
+        
+        logger.info("GoogleRemoteAuthProvider initialized with RemoteAuthProvider pattern")
+    
+    def get_routes(self) -> List[Route]:
+        """
+        Add custom OAuth proxy endpoints to the standard protected resource routes.
+        
+        These endpoints work around Google's CORS restrictions and provide
+        dynamic client registration support.
+        """
+        # Get the standard OAuth protected resource routes from RemoteAuthProvider
+        routes = super().get_routes()
+        
+        # Log what routes we're getting from the parent
+        logger.info(f"GoogleRemoteAuthProvider: Parent provided {len(routes)} routes")
+        for route in routes:
+            logger.info(f"  - {route.path} ({', '.join(route.methods)})")
+        
+        # Add our custom proxy endpoints using common handlers
+        routes.append(Route("/oauth2/authorize", handle_oauth_authorize, methods=["GET", "OPTIONS"]))
+        
+        routes.append(Route("/oauth2/token", handle_proxy_token_exchange, methods=["POST", "OPTIONS"]))
+        
+        routes.append(Route("/oauth2/register", handle_oauth_register, methods=["POST", "OPTIONS"]))
+        
+        routes.append(Route("/.well-known/oauth-authorization-server", handle_oauth_authorization_server, methods=["GET", "OPTIONS"]))
+        
+        routes.append(Route("/.well-known/oauth-client", handle_oauth_client_config, methods=["GET", "OPTIONS"]))
+        
+        return routes
+    
+    async def verify_token(self, token: str) -> Optional[object]:
+        """
+        Override verify_token to handle Google OAuth access tokens.
+        
+        Google OAuth access tokens (ya29.*) are opaque tokens that need to be
+        verified using the tokeninfo endpoint, not JWT verification.
+        """
+        # Check if this is a Google OAuth access token (starts with ya29.)
+        if token.startswith("ya29."):
+            logger.debug("Detected Google OAuth access token, using tokeninfo verification")
+            
+            try:
+                # Verify the access token using Google's tokeninfo endpoint
+                async with aiohttp.ClientSession() as session:
+                    url = f"https://oauth2.googleapis.com/tokeninfo?access_token={token}"
+                    async with session.get(url) as response:
+                        if response.status != 200:
+                            logger.error(f"Token verification failed: {response.status}")
+                            return None
+                        
+                        token_info = await response.json()
+                        
+                        # Verify the token is for our client
+                        if token_info.get("aud") != self.client_id:
+                            logger.error(f"Token audience mismatch: expected {self.client_id}, got {token_info.get('aud')}")
+                            return None
+                        
+                        # Check if token is expired
+                        expires_in = token_info.get("expires_in", 0)
+                        if int(expires_in) <= 0:
+                            logger.error("Token is expired")
+                            return None
+                        
+                        # Create an access token object that matches the expected interface
+                        from types import SimpleNamespace
+                        import time
+                        
+                        # Calculate expires_at timestamp
+                        expires_in = int(token_info.get("expires_in", 0))
+                        expires_at = int(time.time()) + expires_in if expires_in > 0 else 0
+                        
+                        access_token = SimpleNamespace(
+                            claims={
+                                "email": token_info.get("email"),
+                                "sub": token_info.get("sub"),
+                                "aud": token_info.get("aud"),
+                                "scope": token_info.get("scope", ""),
+                            },
+                            scopes=token_info.get("scope", "").split(),
+                            token=token,
+                            expires_at=expires_at,  # Add the expires_at attribute
+                            client_id=self.client_id,  # Add client_id at top level
+                            # Add other required fields
+                            sub=token_info.get("sub", ""),
+                            email=token_info.get("email", "")
+                        )
+                        
+                        user_email = token_info.get("email")
+                        if user_email:
+                            from auth.oauth21_session_store import get_oauth21_session_store
+                            store = get_oauth21_session_store()
+                            session_id = f"google_{token_info.get('sub', 'unknown')}"
+                            
+                            # Try to get FastMCP session ID for binding
+                            mcp_session_id = None
+                            try:
+                                from fastmcp.server.dependencies import get_context
+                                ctx = get_context()
+                                if ctx and hasattr(ctx, 'session_id'):
+                                    mcp_session_id = ctx.session_id
+                                    logger.debug(f"Binding MCP session {mcp_session_id} to user {user_email}")
+                            except Exception:
+                                pass
+                            
+                            # Store session with issuer information
+                            store.store_session(
+                                user_email=user_email,
+                                access_token=token,
+                                scopes=access_token.scopes,
+                                session_id=session_id,
+                                mcp_session_id=mcp_session_id,
+                                issuer="https://accounts.google.com"
+                            )
+                            
+                            logger.info(f"Successfully verified Google OAuth token for user: {user_email}")
+                        
+                        return access_token
+                        
+            except Exception as e:
+                logger.error(f"Error verifying Google OAuth token: {e}")
+                return None
+        
+        else:
+            # For JWT tokens, use parent's JWT verification
+            logger.debug("Using JWT verification for non-OAuth token")
+            access_token = await super().verify_token(token)
+            
+            if access_token and self.client_id:
+                # Extract user information from token claims
+                user_email = access_token.claims.get("email")
+                if user_email:
+                    from auth.oauth21_session_store import get_oauth21_session_store
+                    store = get_oauth21_session_store()
+                    session_id = f"google_{access_token.claims.get('sub', 'unknown')}"
+                    
+                    # Store session with issuer information
+                    store.store_session(
+                        user_email=user_email,
+                        access_token=token,
+                        scopes=access_token.scopes or [],
+                        session_id=session_id,
+                        issuer="https://accounts.google.com"
+                    )
+                    
+                    logger.debug(f"Successfully verified JWT token for user: {user_email}")
+            
+            return access_token
\ No newline at end of file
diff --git a/auth/mcp_session_middleware.py b/auth/mcp_session_middleware.py
new file mode 100644
index 0000000..76c891a
--- /dev/null
+++ b/auth/mcp_session_middleware.py
@@ -0,0 +1,113 @@
+"""
+MCP Session Middleware
+
+This middleware intercepts MCP requests and sets the session context
+for use by tool functions.
+"""
+
+import logging
+from typing import Callable, Any
+
+from starlette.middleware.base import BaseHTTPMiddleware
+from starlette.requests import Request
+
+from auth.oauth21_session_store import (
+    SessionContext,
+    SessionContextManager,
+    extract_session_from_headers,
+)
+# OAuth 2.1 is now handled by FastMCP auth
+
+logger = logging.getLogger(__name__)
+
+
+class MCPSessionMiddleware(BaseHTTPMiddleware):
+    """
+    Middleware that extracts session information from requests and makes it
+    available to MCP tool functions via context variables.
+    """
+    
+    async def dispatch(self, request: Request, call_next: Callable) -> Any:
+        """Process request and set session context."""
+        
+        logger.debug(f"MCPSessionMiddleware processing request: {request.method} {request.url.path}")
+        
+        # Skip non-MCP paths
+        if not request.url.path.startswith("/mcp"):
+            logger.debug(f"Skipping non-MCP path: {request.url.path}")
+            return await call_next(request)
+        
+        session_context = None
+        
+        try:
+            # Extract session information
+            headers = dict(request.headers)
+            session_id = extract_session_from_headers(headers)
+            
+            # Try to get OAuth 2.1 auth context from FastMCP
+            auth_context = None
+            user_email = None
+            mcp_session_id = None
+            
+            # Check for FastMCP auth context
+            if hasattr(request.state, "auth"):
+                auth_context = request.state.auth
+                # Extract user email from auth claims if available
+                if hasattr(auth_context, 'claims') and auth_context.claims:
+                    user_email = auth_context.claims.get('email')
+            
+            # Check for FastMCP session ID (from streamable HTTP transport)
+            if hasattr(request.state, "session_id"):
+                mcp_session_id = request.state.session_id
+                logger.debug(f"Found FastMCP session ID: {mcp_session_id}")
+            
+            # Also check Authorization header for bearer tokens
+            auth_header = headers.get("authorization")
+            if auth_header and auth_header.lower().startswith("bearer ") and not user_email:
+                try:
+                    import jwt
+                    token = auth_header[7:]  # Remove "Bearer " prefix
+                    # Decode without verification to extract email
+                    claims = jwt.decode(token, options={"verify_signature": False})
+                    user_email = claims.get('email')
+                    if user_email:
+                        logger.debug(f"Extracted user email from JWT: {user_email}")
+                except Exception:
+                    pass
+            
+            # Build session context
+            if session_id or auth_context or user_email or mcp_session_id:
+                # Create session ID hierarchy: explicit session_id > Google user session > FastMCP session
+                effective_session_id = session_id
+                if not effective_session_id and user_email:
+                    effective_session_id = f"google_{user_email}"
+                elif not effective_session_id and mcp_session_id:
+                    effective_session_id = mcp_session_id
+                
+                session_context = SessionContext(
+                    session_id=effective_session_id,
+                    user_id=user_email or (auth_context.user_id if auth_context else None),
+                    auth_context=auth_context,
+                    request=request,
+                    metadata={
+                        "path": request.url.path,
+                        "method": request.method,
+                        "user_email": user_email,
+                        "mcp_session_id": mcp_session_id,
+                    }
+                )
+                
+                logger.debug(
+                    f"MCP request with session: session_id={session_context.session_id}, "
+                    f"user_id={session_context.user_id}, path={request.url.path}"
+                )
+            
+            # Process request with session context
+            with SessionContextManager(session_context):
+                response = await call_next(request)
+                return response
+                
+        except Exception as e:
+            logger.error(f"Error in MCP session middleware: {e}")
+            # Continue without session context
+            return await call_next(request)
\ No newline at end of file
diff --git a/auth/oauth21_integration.py b/auth/oauth21_integration.py
new file mode 100644
index 0000000..c17eac5
--- /dev/null
+++ b/auth/oauth21_integration.py
@@ -0,0 +1,246 @@
+"""
+OAuth 2.1 Integration for Google Services
+
+This module provides integration between FastMCP OAuth sessions and Google services,
+allowing authenticated sessions to be passed through to Google API calls.
+"""
+
+import asyncio
+import logging
+from typing import Optional, Tuple, Any, Dict
+
+from googleapiclient.discovery import build
+
+from auth.google_auth import (
+    GoogleAuthenticationError,
+)
+
+logger = logging.getLogger(__name__)
+
+
+class OAuth21GoogleServiceBuilder:
+    """Builds Google services using FastMCP OAuth authenticated sessions."""
+
+    def __init__(self):
+        """
+        Initialize the service builder.
+        """
+        self._service_cache: Dict[str, Tuple[Any, str]] = {}
+
+    def extract_session_from_context(self, context: Optional[Dict[str, Any]] = None) -> Optional[str]:
+        """
+        Extract session ID from various context sources.
+
+        Args:
+            context: Context dictionary that may contain session information
+
+        Returns:
+            Session ID if found, None otherwise
+        """
+        if not context:
+            return None
+
+        # Try to extract from OAuth 2.1 auth context
+        if "auth_context" in context and hasattr(context["auth_context"], "session_id"):
+            return context["auth_context"].session_id
+
+        # Try direct session_id
+        if "session_id" in context:
+            return context["session_id"]
+
+        # Try from request state
+        if "request" in context:
+            request = context["request"]
+            if hasattr(request, "state") and hasattr(request.state, "auth"):
+                auth_ctx = request.state.auth
+                if hasattr(auth_ctx, "session_id"):
+                    return auth_ctx.session_id
+
+        return None
+
+    async def get_authenticated_service_with_session(
+        self,
+        service_name: str,
+        version: str,
+        tool_name: str,
+        user_google_email: str,
+        required_scopes: list[str],
+        session_id: Optional[str] = None,
+        auth_context: Optional[Any] = None,
+    ) -> Tuple[Any, str]:
+        """
+        Get authenticated Google service using OAuth 2.1 session if available.
+
+        Args:
+            service_name: Google service name (e.g., "gmail", "drive")
+            version: API version (e.g., "v1", "v3")
+            tool_name: Name of the tool for logging
+            user_google_email: User's Google email
+            required_scopes: Required OAuth scopes
+            session_id: OAuth 2.1 session ID
+            auth_context: OAuth 2.1 authentication context
+
+        Returns:
+            Tuple of (service instance, actual user email)
+
+        Raises:
+            GoogleAuthenticationError: If authentication fails
+        """
+        cache_key = f"{user_google_email}:{service_name}:{version}:{':'.join(sorted(required_scopes))}"
+
+        # Check cache first
+        if cache_key in self._service_cache:
+            logger.debug(f"[{tool_name}] Using cached service for {user_google_email}")
+            return self._service_cache[cache_key]
+
+        try:
+            # First check the global OAuth 2.1 session store
+            from auth.oauth21_session_store import get_oauth21_session_store
+            store = get_oauth21_session_store()
+            credentials = store.get_credentials(user_google_email)
+
+            if credentials and credentials.valid:
+                logger.info(f"[{tool_name}] Found OAuth 2.1 credentials in global store for {user_google_email}")
+
+                # Build the service
+                service = await asyncio.to_thread(
+                    build, service_name, version, credentials=credentials
+                )
+
+                # Cache the service
+                self._service_cache[cache_key] = (service, user_google_email)
+
+                return service, user_google_email
+
+            # If OAuth 2.1 is not enabled, fall back to legacy authentication
+            if not is_oauth21_enabled():
+                logger.debug(f"[{tool_name}] OAuth 2.1 is not enabled. Falling back to legacy authentication for {user_google_email}")
+                return await get_legacy_auth_service(
+                    service_name=service_name,
+                    version=version,
+                    tool_name=tool_name,
+                    user_google_email=user_google_email,
+                    required_scopes=required_scopes,
+                )
+
+            # If we are here, it means OAuth 2.1 is enabled but credentials are not found
+            logger.error(f"[{tool_name}] OAuth 2.1 is enabled, but no valid credentials found for {user_google_email}")
+            raise GoogleAuthenticationError(
+                f"OAuth 2.1 is enabled, but no valid credentials found for {user_google_email}"
+            )
+
+        except Exception as e:
+            logger.error(f"[{tool_name}] Authentication failed for {user_google_email}: {e}")
+            raise GoogleAuthenticationError(
+                f"Failed to authenticate for {service_name}: {str(e)}"
+            )
+
+    def clear_cache(self):
+        """Clear the service cache."""
+        self._service_cache.clear()
+        logger.debug("Cleared OAuth 2.1 service cache")
+
+
+# Global instance
+_global_service_builder: Optional[OAuth21GoogleServiceBuilder] = None
+
+
+def get_oauth21_service_builder() -> OAuth21GoogleServiceBuilder:
+    """Get the global OAuth 2.1 service builder instance."""
+    global _global_service_builder
+    if _global_service_builder is None:
+        _global_service_builder = OAuth21GoogleServiceBuilder()
+    return _global_service_builder
+
+
+def set_auth_layer(auth_layer):
+    """
+    Legacy compatibility function - no longer needed with FastMCP auth.
+    """
+    logger.info("set_auth_layer called - OAuth is now handled by FastMCP")
+
+
+_oauth21_enabled = False
+
+def is_oauth21_enabled() -> bool:
+    """
+    Check if the OAuth 2.1 authentication layer is active.
+    """
+    global _oauth21_enabled
+    return _oauth21_enabled
+
+
+def enable_oauth21():
+    """
+    Enable the OAuth 2.1 authentication layer.
+    """
+    global _oauth21_enabled
+    _oauth21_enabled = True
+    logger.info("OAuth 2.1 authentication has been enabled.")
+
+
+async def get_legacy_auth_service(
+    service_name: str,
+    version: str,
+    tool_name: str,
+    user_google_email: str,
+    required_scopes: list[str],
+) -> Tuple[Any, str]:
+    """
+    Get authenticated Google service using legacy authentication.
+    """
+    from auth.google_auth import get_authenticated_google_service as legacy_get_service
+
+    return await legacy_get_service(
+        service_name=service_name,
+        version=version,
+        tool_name=tool_name,
+        user_google_email=user_google_email,
+        required_scopes=required_scopes,
+    )
+
+
+async def get_authenticated_google_service_oauth21(
+    service_name: str,
+    version: str,
+    tool_name: str,
+    user_google_email: str,
+    required_scopes: list[str],
+    context: Optional[Dict[str, Any]] = None,
+) -> Tuple[Any, str]:
+    """
+    Enhanced version of get_authenticated_google_service that supports OAuth 2.1.
+
+    This function checks for OAuth 2.1 session context and uses it if available,
+    otherwise falls back to legacy authentication.
+
+    Args:
+        service_name: Google service name
+        version: API version
+        tool_name: Tool name for logging
+        user_google_email: User's Google email
+        required_scopes: Required OAuth scopes
+        context: Optional context containing session information
+
+    Returns:
+        Tuple of (service instance, actual user email)
+    """
+    builder = get_oauth21_service_builder()
+
+    # FastMCP handles context now - extract any session info
+    session_id = None
+    auth_context = None
+
+    if context:
+        session_id = builder.extract_session_from_context(context)
+        auth_context = context.get("auth_context")
+
+    return await builder.get_authenticated_service_with_session(
+        service_name=service_name,
+        version=version,
+        tool_name=tool_name,
+        user_google_email=user_google_email,
+        required_scopes=required_scopes,
+        session_id=session_id,
+        auth_context=auth_context,
+    )
\ No newline at end of file
diff --git a/auth/oauth21_session_store.py b/auth/oauth21_session_store.py
new file mode 100644
index 0000000..a28dac6
--- /dev/null
+++ b/auth/oauth21_session_store.py
@@ -0,0 +1,576 @@
+"""
+OAuth 2.1 Session Store for Google Services
+
+This module provides a global store for OAuth 2.1 authenticated sessions
+that can be accessed by Google service decorators. It also includes
+session context management and credential conversion functionality.
+"""
+
+import contextvars
+import logging
+from typing import Dict, Optional, Any
+from threading import RLock
+from datetime import datetime, timedelta
+from dataclasses import dataclass
+
+from google.oauth2.credentials import Credentials
+
+logger = logging.getLogger(__name__)
+
+
+# =============================================================================
+# Session Context Management (absorbed from session_context.py)
+# =============================================================================
+
+# Context variable to store the current session information
+_current_session_context: contextvars.ContextVar[Optional['SessionContext']] = contextvars.ContextVar(
+    'current_session_context',
+    default=None
+)
+
+
+@dataclass
+class SessionContext:
+    """Container for session-related information."""
+    session_id: Optional[str] = None
+    user_id: Optional[str] = None
+    auth_context: Optional[Any] = None
+    request: Optional[Any] = None
+    metadata: Dict[str, Any] = None
+    issuer: Optional[str] = None
+
+    def __post_init__(self):
+        if self.metadata is None:
+            self.metadata = {}
+
+
+def set_session_context(context: Optional[SessionContext]):
+    """
+    Set the current session context.
+
+    Args:
+        context: The session context to set
+    """
+    _current_session_context.set(context)
+    if context:
+        logger.debug(f"Set session context: session_id={context.session_id}, user_id={context.user_id}")
+    else:
+        logger.debug("Cleared session context")
+
+
+def get_session_context() -> Optional[SessionContext]:
+    """
+    Get the current session context.
+
+    Returns:
+        The current session context or None
+    """
+    return _current_session_context.get()
+
+
+def clear_session_context():
+    """Clear the current session context."""
+    set_session_context(None)
+
+
+class SessionContextManager:
+    """
+    Context manager for temporarily setting session context.
+
+    Usage:
+        with SessionContextManager(session_context):
+            # Code that needs access to session context
+            pass
+    """
+
+    def __init__(self, context: Optional[SessionContext]):
+        self.context = context
+        self.token = None
+
+    def __enter__(self):
+        """Set the session context."""
+        self.token = _current_session_context.set(self.context)
+        return self.context
+
+    def __exit__(self, exc_type, exc_val, exc_tb):
+        """Reset the session context."""
+        if self.token:
+            _current_session_context.reset(self.token)
+
+
+def extract_session_from_headers(headers: Dict[str, str]) -> Optional[str]:
+    """
+    Extract session ID from request headers.
+
+    Args:
+        headers: Request headers
+
+    Returns:
+        Session ID if found
+    """
+    # Try different header names
+    session_id = headers.get("mcp-session-id") or headers.get("Mcp-Session-Id")
+    if session_id:
+        return session_id
+
+    session_id = headers.get("x-session-id") or headers.get("X-Session-ID")
+    if session_id:
+        return session_id
+
+    # Try Authorization header for Bearer token
+    auth_header = headers.get("authorization") or headers.get("Authorization")
+    if auth_header and auth_header.lower().startswith("bearer "):
+        # Extract bearer token and try to find associated session
+        token = auth_header[7:]  # Remove "Bearer " prefix
+        if token:
+            # Look for a session that has this access token
+            # This requires scanning sessions, but bearer tokens should be unique
+            store = get_oauth21_session_store()
+            for user_email, session_info in store._sessions.items():
+                if session_info.get("access_token") == token:
+                    return session_info.get("session_id") or f"bearer_{user_email}"
+        
+        # If no session found, create a temporary session ID from token hash
+        # This allows header-based authentication to work with session context
+        import hashlib
+        token_hash = hashlib.sha256(token.encode()).hexdigest()[:8]
+        return f"bearer_token_{token_hash}"
+
+    return None
+
+
+# =============================================================================
+# OAuth21SessionStore - Main Session Management
+# =============================================================================
+
+class OAuth21SessionStore:
+    """
+    Global store for OAuth 2.1 authenticated sessions.
+    
+    This store maintains a mapping of user emails to their OAuth 2.1
+    authenticated credentials, allowing Google services to access them.
+    It also maintains a mapping from FastMCP session IDs to user emails.
+    
+    Security: Sessions are bound to specific users and can only access
+    their own credentials.
+    """
+    
+    def __init__(self):
+        self._sessions: Dict[str, Dict[str, Any]] = {}
+        self._mcp_session_mapping: Dict[str, str] = {}  # Maps FastMCP session ID -> user email
+        self._session_auth_binding: Dict[str, str] = {}  # Maps session ID -> authenticated user email (immutable)
+        self._lock = RLock()
+    
+    def store_session(
+        self,
+        user_email: str,
+        access_token: str,
+        refresh_token: Optional[str] = None,
+        token_uri: str = "https://oauth2.googleapis.com/token",
+        client_id: Optional[str] = None,
+        client_secret: Optional[str] = None,
+        scopes: Optional[list] = None,
+        expiry: Optional[Any] = None,
+        session_id: Optional[str] = None,
+        mcp_session_id: Optional[str] = None,
+        issuer: Optional[str] = None,
+    ):
+        """
+        Store OAuth 2.1 session information.
+        
+        Args:
+            user_email: User's email address
+            access_token: OAuth 2.1 access token
+            refresh_token: OAuth 2.1 refresh token
+            token_uri: Token endpoint URI
+            client_id: OAuth client ID
+            client_secret: OAuth client secret
+            scopes: List of granted scopes
+            expiry: Token expiry time
+            session_id: OAuth 2.1 session ID
+            mcp_session_id: FastMCP session ID to map to this user
+            issuer: Token issuer (e.g., "https://accounts.google.com")
+        """
+        with self._lock:
+            session_info = {
+                "access_token": access_token,
+                "refresh_token": refresh_token,
+                "token_uri": token_uri,
+                "client_id": client_id,
+                "client_secret": client_secret,
+                "scopes": scopes or [],
+                "expiry": expiry,
+                "session_id": session_id,
+                "mcp_session_id": mcp_session_id,
+                "issuer": issuer,
+            }
+            
+            self._sessions[user_email] = session_info
+            
+            # Store MCP session mapping if provided
+            if mcp_session_id:
+                # Create immutable session binding (first binding wins, cannot be changed)
+                if mcp_session_id not in self._session_auth_binding:
+                    self._session_auth_binding[mcp_session_id] = user_email
+                    logger.info(f"Created immutable session binding: {mcp_session_id} -> {user_email}")
+                elif self._session_auth_binding[mcp_session_id] != user_email:
+                    # Security: Attempt to bind session to different user
+                    logger.error(f"SECURITY: Attempt to rebind session {mcp_session_id} from {self._session_auth_binding[mcp_session_id]} to {user_email}")
+                    raise ValueError(f"Session {mcp_session_id} is already bound to a different user")
+                
+                self._mcp_session_mapping[mcp_session_id] = user_email
+                logger.info(f"Stored OAuth 2.1 session for {user_email} (session_id: {session_id}, mcp_session_id: {mcp_session_id})")
+            else:
+                logger.info(f"Stored OAuth 2.1 session for {user_email} (session_id: {session_id})")
+            
+            # Also create binding for the OAuth session ID
+            if session_id and session_id not in self._session_auth_binding:
+                self._session_auth_binding[session_id] = user_email
+    
+    def get_credentials(self, user_email: str) -> Optional[Credentials]:
+        """
+        Get Google credentials for a user from OAuth 2.1 session.
+        
+        Args:
+            user_email: User's email address
+            
+        Returns:
+            Google Credentials object or None
+        """
+        with self._lock:
+            session_info = self._sessions.get(user_email)
+            if not session_info:
+                logger.debug(f"No OAuth 2.1 session found for {user_email}")
+                return None
+            
+            try:
+                # Create Google credentials from session info
+                credentials = Credentials(
+                    token=session_info["access_token"],
+                    refresh_token=session_info.get("refresh_token"),
+                    token_uri=session_info["token_uri"],
+                    client_id=session_info.get("client_id"),
+                    client_secret=session_info.get("client_secret"),
+                    scopes=session_info.get("scopes", []),
+                    expiry=session_info.get("expiry"),
+                )
+                
+                logger.debug(f"Retrieved OAuth 2.1 credentials for {user_email}")
+                return credentials
+                
+            except Exception as e:
+                logger.error(f"Failed to create credentials for {user_email}: {e}")
+                return None
+    
+    def get_credentials_by_mcp_session(self, mcp_session_id: str) -> Optional[Credentials]:
+        """
+        Get Google credentials using FastMCP session ID.
+        
+        Args:
+            mcp_session_id: FastMCP session ID
+            
+        Returns:
+            Google Credentials object or None
+        """
+        with self._lock:
+            # Look up user email from MCP session mapping
+            user_email = self._mcp_session_mapping.get(mcp_session_id)
+            if not user_email:
+                logger.debug(f"No user mapping found for MCP session {mcp_session_id}")
+                return None
+            
+            logger.debug(f"Found user {user_email} for MCP session {mcp_session_id}")
+            return self.get_credentials(user_email)
+    
+    def get_credentials_with_validation(
+        self, 
+        requested_user_email: str, 
+        session_id: Optional[str] = None,
+        auth_token_email: Optional[str] = None,
+        allow_recent_auth: bool = False
+    ) -> Optional[Credentials]:
+        """
+        Get Google credentials with session validation.
+        
+        This method ensures that a session can only access credentials for its
+        authenticated user, preventing cross-account access.
+        
+        Args:
+            requested_user_email: The email of the user whose credentials are requested
+            session_id: The current session ID (MCP or OAuth session)
+            auth_token_email: Email from the verified auth token (if available)
+            
+        Returns:
+            Google Credentials object if validation passes, None otherwise
+        """
+        with self._lock:
+            # Priority 1: Check auth token email (most secure, from verified JWT)
+            if auth_token_email:
+                if auth_token_email != requested_user_email:
+                    logger.error(
+                        f"SECURITY VIOLATION: Token for {auth_token_email} attempted to access "
+                        f"credentials for {requested_user_email}"
+                    )
+                    return None
+                # Token email matches, allow access
+                return self.get_credentials(requested_user_email)
+            
+            # Priority 2: Check session binding
+            if session_id:
+                bound_user = self._session_auth_binding.get(session_id)
+                if bound_user:
+                    if bound_user != requested_user_email:
+                        logger.error(
+                            f"SECURITY VIOLATION: Session {session_id} (bound to {bound_user}) "
+                            f"attempted to access credentials for {requested_user_email}"
+                        )
+                        return None
+                    # Session binding matches, allow access
+                    return self.get_credentials(requested_user_email)
+                
+                # Check if this is an MCP session
+                mcp_user = self._mcp_session_mapping.get(session_id)
+                if mcp_user:
+                    if mcp_user != requested_user_email:
+                        logger.error(
+                            f"SECURITY VIOLATION: MCP session {session_id} (user {mcp_user}) "
+                            f"attempted to access credentials for {requested_user_email}"
+                        )
+                        return None
+                    # MCP session matches, allow access
+                    return self.get_credentials(requested_user_email)
+            
+            # Special case: Allow access if user has recently authenticated (for clients that don't send tokens)
+            # CRITICAL SECURITY: This is ONLY allowed in stdio mode, NEVER in OAuth 2.1 mode
+            if allow_recent_auth and requested_user_email in self._sessions:
+                # Check transport mode to ensure this is only used in stdio
+                try:
+                    from core.config import get_transport_mode
+                    transport_mode = get_transport_mode()
+                    if transport_mode != "stdio":
+                        logger.error(
+                            f"SECURITY: Attempted to use allow_recent_auth in {transport_mode} mode. "
+                            f"This is only allowed in stdio mode!"
+                        )
+                        return None
+                except Exception as e:
+                    logger.error(f"Failed to check transport mode: {e}")
+                    return None
+                
+                logger.info(
+                    f"Allowing credential access for {requested_user_email} based on recent authentication "
+                    f"(stdio mode only - client not sending bearer token)"
+                )
+                return self.get_credentials(requested_user_email)
+            
+            # No session or token info available - deny access for security
+            logger.warning(
+                f"Credential access denied for {requested_user_email}: No valid session or token"
+            )
+            return None
+    
+    def get_user_by_mcp_session(self, mcp_session_id: str) -> Optional[str]:
+        """
+        Get user email by FastMCP session ID.
+        
+        Args:
+            mcp_session_id: FastMCP session ID
+            
+        Returns:
+            User email or None
+        """
+        with self._lock:
+            return self._mcp_session_mapping.get(mcp_session_id)
+    
+    def get_session_info(self, user_email: str) -> Optional[Dict[str, Any]]:
+        """
+        Get complete session information including issuer.
+        
+        Args:
+            user_email: User's email address
+            
+        Returns:
+            Session information dictionary or None
+        """
+        with self._lock:
+            return self._sessions.get(user_email)
+    
+    def remove_session(self, user_email: str):
+        """Remove session for a user."""
+        with self._lock:
+            if user_email in self._sessions:
+                # Get session IDs to clean up mappings
+                session_info = self._sessions.get(user_email, {})
+                mcp_session_id = session_info.get("mcp_session_id")
+                session_id = session_info.get("session_id")
+                
+                # Remove from sessions
+                del self._sessions[user_email]
+                
+                # Remove from MCP mapping if exists
+                if mcp_session_id and mcp_session_id in self._mcp_session_mapping:
+                    del self._mcp_session_mapping[mcp_session_id]
+                    # Also remove from auth binding
+                    if mcp_session_id in self._session_auth_binding:
+                        del self._session_auth_binding[mcp_session_id]
+                    logger.info(f"Removed OAuth 2.1 session for {user_email} and MCP mapping for {mcp_session_id}")
+                
+                # Remove OAuth session binding if exists
+                if session_id and session_id in self._session_auth_binding:
+                    del self._session_auth_binding[session_id]
+                
+                if not mcp_session_id:
+                    logger.info(f"Removed OAuth 2.1 session for {user_email}")
+    
+    def has_session(self, user_email: str) -> bool:
+        """Check if a user has an active session."""
+        with self._lock:
+            return user_email in self._sessions
+    
+    def has_mcp_session(self, mcp_session_id: str) -> bool:
+        """Check if an MCP session has an associated user session."""
+        with self._lock:
+            return mcp_session_id in self._mcp_session_mapping
+    
+    def get_stats(self) -> Dict[str, Any]:
+        """Get store statistics."""
+        with self._lock:
+            return {
+                "total_sessions": len(self._sessions),
+                "users": list(self._sessions.keys()),
+                "mcp_session_mappings": len(self._mcp_session_mapping),
+                "mcp_sessions": list(self._mcp_session_mapping.keys()),
+            }
+
+
+# Global instance
+_global_store = OAuth21SessionStore()
+
+
+def get_oauth21_session_store() -> OAuth21SessionStore:
+    """Get the global OAuth 2.1 session store."""
+    return _global_store
+
+
+# =============================================================================
+# Google Credentials Bridge (absorbed from oauth21_google_bridge.py)
+# =============================================================================
+
+# Global auth provider instance (set during server initialization)
+_auth_provider = None
+
+
+def set_auth_provider(provider):
+    """Set the global auth provider instance."""
+    global _auth_provider
+    _auth_provider = provider
+    logger.info("OAuth 2.1 auth provider configured for Google credential bridging")
+
+
+def get_auth_provider():
+    """Get the global auth provider instance."""
+    return _auth_provider
+
+
+def get_credentials_from_token(access_token: str, user_email: Optional[str] = None) -> Optional[Credentials]:
+    """
+    Convert a bearer token to Google credentials.
+    
+    Args:
+        access_token: The bearer token
+        user_email: Optional user email for session lookup
+        
+    Returns:
+        Google Credentials object or None
+    """
+    if not _auth_provider:
+        logger.error("Auth provider not configured")
+        return None
+        
+    try:
+        store = get_oauth21_session_store()
+        
+        # If we have user_email, try to get credentials from store
+        if user_email:
+            credentials = store.get_credentials(user_email)
+            if credentials and credentials.token == access_token:
+                logger.debug(f"Found matching credentials from store for {user_email}")
+                return credentials
+        
+        # Otherwise, create minimal credentials with just the access token
+        # Assume token is valid for 1 hour (typical for Google tokens)
+        expiry = datetime.utcnow() + timedelta(hours=1)
+        
+        credentials = Credentials(
+            token=access_token,
+            refresh_token=None,
+            token_uri="https://oauth2.googleapis.com/token",
+            client_id=_auth_provider.client_id,
+            client_secret=_auth_provider.client_secret,
+            scopes=None,  # Will be populated from token claims if available
+            expiry=expiry
+        )
+        
+        logger.debug("Created Google credentials from bearer token")
+        return credentials
+        
+    except Exception as e:
+        logger.error(f"Failed to create Google credentials from token: {e}")
+        return None
+
+
+def store_token_session(token_response: dict, user_email: str, mcp_session_id: Optional[str] = None) -> str:
+    """
+    Store a token response in the session store.
+    
+    Args:
+        token_response: OAuth token response from Google
+        user_email: User's email address
+        mcp_session_id: Optional FastMCP session ID to map to this user
+        
+    Returns:
+        Session ID
+    """
+    if not _auth_provider:
+        logger.error("Auth provider not configured")
+        return ""
+        
+    try:
+        # Try to get FastMCP session ID from context if not provided
+        if not mcp_session_id:
+            try:
+                from core.context import get_fastmcp_session_id
+                mcp_session_id = get_fastmcp_session_id()
+                if mcp_session_id:
+                    logger.debug(f"Got FastMCP session ID from context: {mcp_session_id}")
+            except Exception as e:
+                logger.debug(f"Could not get FastMCP session from context: {e}")
+        
+        # Store session in OAuth21SessionStore
+        store = get_oauth21_session_store()
+        
+        session_id = f"google_{user_email}"
+        store.store_session(
+            user_email=user_email,
+            access_token=token_response.get("access_token"),
+            refresh_token=token_response.get("refresh_token"),
+            token_uri="https://oauth2.googleapis.com/token",
+            client_id=_auth_provider.client_id,
+            client_secret=_auth_provider.client_secret,
+            scopes=token_response.get("scope", "").split() if token_response.get("scope") else None,
+            expiry=datetime.utcnow() + timedelta(seconds=token_response.get("expires_in", 3600)),
+            session_id=session_id,
+            mcp_session_id=mcp_session_id,
+            issuer="https://accounts.google.com",  # Add issuer for Google tokens
+        )
+        
+        if mcp_session_id:
+            logger.info(f"Stored token session for {user_email} with MCP session {mcp_session_id}")
+        else:
+            logger.info(f"Stored token session for {user_email}")
+        
+        return session_id
+        
+    except Exception as e:
+        logger.error(f"Failed to store token session: {e}")
+        return ""
\ No newline at end of file
diff --git a/auth/oauth_callback_server.py b/auth/oauth_callback_server.py
index e8ecc88..32d48c9 100644
--- a/auth/oauth_callback_server.py
+++ b/auth/oauth_callback_server.py
@@ -17,7 +17,7 @@ from fastapi import FastAPI, Request
 from typing import Optional
 from urllib.parse import urlparse
 
-from auth.google_auth import handle_auth_callback, check_client_secrets
+# Import moved inside functions to avoid circular import
 from auth.scopes import SCOPES
 from auth.oauth_responses import create_error_response, create_success_response, create_server_error_response
 
@@ -62,6 +62,7 @@ class MinimalOAuthServer:
 
             try:
                 # Check if we have credentials available (environment variables or file)
+                from auth.google_auth import check_client_secrets
                 error_message = check_client_secrets()
                 if error_message:
                     return create_server_error_response(error_message)
@@ -71,6 +72,7 @@ class MinimalOAuthServer:
                 # Session ID tracking removed - not needed
 
                 # Exchange code for credentials
+                from auth.google_auth import handle_auth_callback
                 redirect_uri = get_oauth_redirect_uri(port=self.port, base_uri=self.base_uri)
                 verified_user_id, credentials = handle_auth_callback(
                     scopes=SCOPES,
diff --git a/auth/oauth_common_handlers.py b/auth/oauth_common_handlers.py
new file mode 100644
index 0000000..fa4cdcd
--- /dev/null
+++ b/auth/oauth_common_handlers.py
@@ -0,0 +1,417 @@
+"""Common OAuth 2.1 request handlers used by both legacy and modern auth providers."""
+
+import logging
+import os
+import time
+from datetime import datetime, timedelta
+from urllib.parse import urlencode, parse_qs
+
+import aiohttp
+import jwt
+from jwt import PyJWKClient
+from starlette.requests import Request
+from starlette.responses import JSONResponse, RedirectResponse
+from google.oauth2.credentials import Credentials
+
+from auth.oauth21_session_store import store_token_session
+from auth.google_auth import save_credentials_to_file
+from auth.scopes import SCOPES
+from core.config import WORKSPACE_MCP_BASE_URI, WORKSPACE_MCP_PORT
+
+logger = logging.getLogger(__name__)
+
+
+async def handle_oauth_authorize(request: Request):
+    """Common handler for OAuth authorization proxy."""
+    if request.method == "OPTIONS":
+        return JSONResponse(
+            content={},
+            headers={
+                "Access-Control-Allow-Origin": "*",
+                "Access-Control-Allow-Methods": "GET, OPTIONS",
+                "Access-Control-Allow-Headers": "Content-Type"
+            }
+        )
+
+    # Get query parameters
+    params = dict(request.query_params)
+
+    # Add our client ID if not provided
+    client_id = os.getenv("GOOGLE_OAUTH_CLIENT_ID")
+    if "client_id" not in params and client_id:
+        params["client_id"] = client_id
+
+    # Ensure response_type is code
+    params["response_type"] = "code"
+
+    # Merge client scopes with our full SCOPES list
+    client_scopes = params.get("scope", "").split() if params.get("scope") else []
+    # Always include all Google Workspace scopes for full functionality
+    all_scopes = set(client_scopes) | set(SCOPES)
+    params["scope"] = " ".join(sorted(all_scopes))
+    logger.info(f"OAuth 2.1 authorization: Requesting scopes: {params['scope']}")
+
+    # Build Google authorization URL
+    google_auth_url = "https://accounts.google.com/o/oauth2/v2/auth?" + urlencode(params)
+
+    # Return redirect
+    return RedirectResponse(
+        url=google_auth_url,
+        status_code=302,
+        headers={
+            "Access-Control-Allow-Origin": "*"
+        }
+    )
+
+
+async def handle_proxy_token_exchange(request: Request):
+    """Common handler for OAuth token exchange proxy."""
+    if request.method == "OPTIONS":
+        return JSONResponse(
+            content={},
+            headers={
+                "Access-Control-Allow-Origin": "*",
+                "Access-Control-Allow-Methods": "POST, OPTIONS",
+                "Access-Control-Allow-Headers": "Content-Type, Authorization"
+            }
+        )
+    try:
+        # Get form data
+        body = await request.body()
+        content_type = request.headers.get("content-type", "application/x-www-form-urlencoded")
+        
+        # Parse form data to add missing client credentials
+        if content_type and "application/x-www-form-urlencoded" in content_type:
+            form_data = parse_qs(body.decode('utf-8'))
+            
+            # Check if client_id is missing (public client)
+            if 'client_id' not in form_data or not form_data['client_id'][0]:
+                client_id = os.getenv("GOOGLE_OAUTH_CLIENT_ID")
+                if client_id:
+                    form_data['client_id'] = [client_id]
+                    logger.debug("Added missing client_id to token request")
+            
+            # Check if client_secret is missing (public client using PKCE)
+            if 'client_secret' not in form_data:
+                client_secret = os.getenv("GOOGLE_OAUTH_CLIENT_SECRET")
+                if client_secret:
+                    form_data['client_secret'] = [client_secret]
+                    logger.debug("Added missing client_secret to token request")
+            
+            # Reconstruct body with added credentials
+            body = urlencode(form_data, doseq=True).encode('utf-8')
+
+        # Forward request to Google
+        async with aiohttp.ClientSession() as session:
+            headers = {"Content-Type": content_type}
+
+            async with session.post("https://oauth2.googleapis.com/token", data=body, headers=headers) as response:
+                response_data = await response.json()
+
+                # Log for debugging
+                if response.status != 200:
+                    logger.error(f"Token exchange failed: {response.status} - {response_data}")
+                else:
+                    logger.info("Token exchange successful")
+
+                    # Store the token session for credential bridging
+                    if "access_token" in response_data:
+                        try:
+                            # Extract user email from ID token if present
+                            if "id_token" in response_data:
+                                # Verify ID token using Google's public keys for security
+                                try:
+                                    # Get Google's public keys for verification
+                                    jwks_client = PyJWKClient("https://www.googleapis.com/oauth2/v3/certs")
+
+                                    # Get signing key from JWT header
+                                    signing_key = jwks_client.get_signing_key_from_jwt(response_data["id_token"])
+
+                                    # Verify and decode the ID token
+                                    id_token_claims = jwt.decode(
+                                        response_data["id_token"],
+                                        signing_key.key,
+                                        algorithms=["RS256"],
+                                        audience=os.getenv("GOOGLE_OAUTH_CLIENT_ID"),
+                                        issuer="https://accounts.google.com"
+                                    )
+                                    user_email = id_token_claims.get("email")
+                                    
+                                    if user_email:
+                                        # Try to get FastMCP session ID from request context for binding
+                                        mcp_session_id = None
+                                        try:
+                                            # Check if this is a streamable HTTP request with session
+                                            if hasattr(request, 'state') and hasattr(request.state, 'session_id'):
+                                                mcp_session_id = request.state.session_id
+                                                logger.info(f"Found MCP session ID for binding: {mcp_session_id}")
+                                        except Exception as e:
+                                            logger.debug(f"Could not get MCP session ID: {e}")
+                                        
+                                        # Store the token session with MCP session binding
+                                        session_id = store_token_session(response_data, user_email, mcp_session_id)
+                                        logger.info(f"Stored OAuth session for {user_email} (session: {session_id}, mcp: {mcp_session_id})")
+
+                                        # Also create and store Google credentials
+                                        expiry = None
+                                        if "expires_in" in response_data:
+                                            # Google auth library expects timezone-naive datetime
+                                            expiry = datetime.utcnow() + timedelta(seconds=response_data["expires_in"])
+
+                                        credentials = Credentials(
+                                            token=response_data["access_token"],
+                                            refresh_token=response_data.get("refresh_token"),
+                                            token_uri="https://oauth2.googleapis.com/token",
+                                            client_id=os.getenv("GOOGLE_OAUTH_CLIENT_ID"),
+                                            client_secret=os.getenv("GOOGLE_OAUTH_CLIENT_SECRET"),
+                                            scopes=response_data.get("scope", "").split() if response_data.get("scope") else None,
+                                            expiry=expiry
+                                        )
+
+                                        # Save credentials to file for legacy auth
+                                        save_credentials_to_file(user_email, credentials)
+                                        logger.info(f"Saved Google credentials for {user_email}")
+                                except jwt.ExpiredSignatureError:
+                                    logger.error("ID token has expired - cannot extract user email")
+                                except jwt.InvalidTokenError as e:
+                                    logger.error(f"Invalid ID token - cannot extract user email: {e}")
+                                except Exception as e:
+                                    logger.error(f"Failed to verify ID token - cannot extract user email: {e}")
+
+                        except Exception as e:
+                            logger.error(f"Failed to store OAuth session: {e}")
+
+                return JSONResponse(
+                    status_code=response.status,
+                    content=response_data,
+                    headers={
+                        "Content-Type": "application/json",
+                        "Access-Control-Allow-Origin": "*",
+                        "Cache-Control": "no-store"
+                    }
+                )
+
+    except Exception as e:
+        logger.error(f"Error in token proxy: {e}")
+        return JSONResponse(
+            status_code=500,
+            content={"error": "server_error", "error_description": str(e)},
+            headers={"Access-Control-Allow-Origin": "*"}
+        )
+
+
+async def handle_oauth_protected_resource(request: Request):
+    """Common handler for OAuth protected resource metadata."""
+    if request.method == "OPTIONS":
+        return JSONResponse(
+            content={},
+            headers={
+                "Access-Control-Allow-Origin": "*",
+                "Access-Control-Allow-Methods": "GET, OPTIONS",
+                "Access-Control-Allow-Headers": "Content-Type"
+            }
+        )
+
+    metadata = {
+        "resource": f"{WORKSPACE_MCP_BASE_URI}:{WORKSPACE_MCP_PORT}",
+        "authorization_servers": [
+            f"{WORKSPACE_MCP_BASE_URI}:{WORKSPACE_MCP_PORT}"
+        ],
+        "bearer_methods_supported": ["header"],
+        "scopes_supported": SCOPES,
+        "resource_documentation": "https://developers.google.com/workspace",
+        "client_registration_required": True,
+        "client_configuration_endpoint": f"{WORKSPACE_MCP_BASE_URI}:{WORKSPACE_MCP_PORT}/.well-known/oauth-client",
+    }
+
+    return JSONResponse(
+        content=metadata,
+        headers={
+            "Content-Type": "application/json",
+            "Access-Control-Allow-Origin": "*"
+        }
+    )
+
+
+async def handle_oauth_authorization_server(request: Request):
+    """Common handler for OAuth authorization server metadata."""
+    if request.method == "OPTIONS":
+        return JSONResponse(
+            content={},
+            headers={
+                "Access-Control-Allow-Origin": "*",
+                "Access-Control-Allow-Methods": "GET, OPTIONS",
+                "Access-Control-Allow-Headers": "Content-Type"
+            }
+        )
+
+    try:
+        # Fetch metadata from Google
+        async with aiohttp.ClientSession() as session:
+            url = "https://accounts.google.com/.well-known/openid-configuration"
+            async with session.get(url) as response:
+                if response.status == 200:
+                    metadata = await response.json()
+
+                    # Add OAuth 2.1 required fields
+                    metadata.setdefault("code_challenge_methods_supported", ["S256"])
+                    metadata.setdefault("pkce_required", True)
+
+                    # Override endpoints to use our proxies
+                    metadata["token_endpoint"] = f"{WORKSPACE_MCP_BASE_URI}:{WORKSPACE_MCP_PORT}/oauth2/token"
+                    metadata["authorization_endpoint"] = f"{WORKSPACE_MCP_BASE_URI}:{WORKSPACE_MCP_PORT}/oauth2/authorize"
+                    metadata["enable_dynamic_registration"] = True
+                    metadata["registration_endpoint"] = f"{WORKSPACE_MCP_BASE_URI}:{WORKSPACE_MCP_PORT}/oauth2/register"
+                    return JSONResponse(
+                        content=metadata,
+                        headers={
+                            "Content-Type": "application/json",
+                            "Access-Control-Allow-Origin": "*"
+                        }
+                    )
+
+        # Fallback metadata
+        return JSONResponse(
+            content={
+                "issuer": "https://accounts.google.com",
+                "authorization_endpoint": f"{WORKSPACE_MCP_BASE_URI}:{WORKSPACE_MCP_PORT}/oauth2/authorize",
+                "token_endpoint": f"{WORKSPACE_MCP_BASE_URI}:{WORKSPACE_MCP_PORT}/oauth2/token",
+                "userinfo_endpoint": "https://www.googleapis.com/oauth2/v2/userinfo",
+                "revocation_endpoint": "https://oauth2.googleapis.com/revoke",
+                "jwks_uri": "https://www.googleapis.com/oauth2/v3/certs",
+                "response_types_supported": ["code"],
+                "code_challenge_methods_supported": ["S256"],
+                "pkce_required": True,
+                "grant_types_supported": ["authorization_code", "refresh_token"],
+                "scopes_supported": SCOPES,
+                "token_endpoint_auth_methods_supported": ["client_secret_basic", "client_secret_post"]
+            },
+            headers={
+                "Content-Type": "application/json",
+                "Access-Control-Allow-Origin": "*"
+            }
+        )
+
+    except Exception as e:
+        logger.error(f"Error fetching auth server metadata: {e}")
+        return JSONResponse(
+            status_code=500,
+            content={"error": "Failed to fetch authorization server metadata"},
+            headers={"Access-Control-Allow-Origin": "*"}
+        )
+
+
+async def handle_oauth_client_config(request: Request):
+    """Common handler for OAuth client configuration."""
+    if request.method == "OPTIONS":
+        return JSONResponse(
+            content={},
+            headers={
+                "Access-Control-Allow-Origin": "*",
+                "Access-Control-Allow-Methods": "GET, OPTIONS",
+                "Access-Control-Allow-Headers": "Content-Type"
+            }
+        )
+
+    client_id = os.getenv("GOOGLE_OAUTH_CLIENT_ID")
+    if not client_id:
+        return JSONResponse(
+            status_code=404,
+            content={"error": "OAuth not configured"},
+            headers={"Access-Control-Allow-Origin": "*"}
+        )
+
+    return JSONResponse(
+        content={
+            "client_id": client_id,
+            "client_name": "Google Workspace MCP Server",
+            "client_uri": f"{WORKSPACE_MCP_BASE_URI}:{WORKSPACE_MCP_PORT}",
+            "redirect_uris": [
+                f"{WORKSPACE_MCP_BASE_URI}:{WORKSPACE_MCP_PORT}/oauth2callback",
+                "http://localhost:5173/auth/callback"
+            ],
+            "grant_types": ["authorization_code", "refresh_token"],
+            "response_types": ["code"],
+            "scope": " ".join(SCOPES),
+            "token_endpoint_auth_method": "client_secret_basic",
+            "code_challenge_methods": ["S256"]
+        },
+        headers={
+            "Content-Type": "application/json",
+            "Access-Control-Allow-Origin": "*"
+        }
+    )
+
+
+async def handle_oauth_register(request: Request):
+    """Common handler for OAuth dynamic client registration."""
+    if request.method == "OPTIONS":
+        return JSONResponse(
+            content={},
+            headers={
+                "Access-Control-Allow-Origin": "*",
+                "Access-Control-Allow-Methods": "POST, OPTIONS",
+                "Access-Control-Allow-Headers": "Content-Type, Authorization"
+            }
+        )
+
+    client_id = os.getenv("GOOGLE_OAUTH_CLIENT_ID")
+    client_secret = os.getenv("GOOGLE_OAUTH_CLIENT_SECRET")
+
+    if not client_id or not client_secret:
+        return JSONResponse(
+            status_code=400,
+            content={"error": "invalid_request", "error_description": "OAuth not configured"},
+            headers={"Access-Control-Allow-Origin": "*"}
+        )
+
+    try:
+        # Parse the registration request
+        body = await request.json()
+        logger.info(f"Dynamic client registration request received: {body}")
+
+        # Extract redirect URIs from the request or use defaults
+        redirect_uris = body.get("redirect_uris", [])
+        if not redirect_uris:
+            redirect_uris = [
+                f"{WORKSPACE_MCP_BASE_URI}:{WORKSPACE_MCP_PORT}/oauth2callback",
+                "http://localhost:5173/auth/callback"
+            ]
+
+        # Build the registration response with our pre-configured credentials
+        response_data = {
+            "client_id": client_id,
+            "client_secret": client_secret,
+            "client_name": body.get("client_name", "Google Workspace MCP Server"),
+            "client_uri": body.get("client_uri", f"{WORKSPACE_MCP_BASE_URI}:{WORKSPACE_MCP_PORT}"),
+            "redirect_uris": redirect_uris,
+            "grant_types": body.get("grant_types", ["authorization_code", "refresh_token"]),
+            "response_types": body.get("response_types", ["code"]),
+            "scope": body.get("scope", " ".join(SCOPES)),
+            "token_endpoint_auth_method": body.get("token_endpoint_auth_method", "client_secret_basic"),
+            "code_challenge_methods": ["S256"],
+            # Additional OAuth 2.1 fields
+            "client_id_issued_at": int(time.time()),
+            "registration_access_token": "not-required",  # We don't implement client management
+            "registration_client_uri": f"{WORKSPACE_MCP_BASE_URI}:{WORKSPACE_MCP_PORT}/oauth2/register/{client_id}"
+        }
+
+        logger.info("Dynamic client registration successful - returning pre-configured Google credentials")
+
+        return JSONResponse(
+            status_code=201,
+            content=response_data,
+            headers={
+                "Content-Type": "application/json",
+                "Access-Control-Allow-Origin": "*",
+                "Cache-Control": "no-store"
+            }
+        )
+
+    except Exception as e:
+        logger.error(f"Error in dynamic client registration: {e}")
+        return JSONResponse(
+            status_code=400,
+            content={"error": "invalid_request", "error_description": str(e)},
+            headers={"Access-Control-Allow-Origin": "*"}
+        )
\ No newline at end of file
diff --git a/auth/scopes.py b/auth/scopes.py
index 516c20e..43eaa54 100644
--- a/auth/scopes.py
+++ b/auth/scopes.py
@@ -10,11 +10,14 @@ logger = logging.getLogger(__name__)
 
 # Individual OAuth Scope Constants
 USERINFO_EMAIL_SCOPE = 'https://www.googleapis.com/auth/userinfo.email'
+USERINFO_PROFILE_SCOPE = 'https://www.googleapis.com/auth/userinfo.profile'
 OPENID_SCOPE = 'openid'
+CALENDAR_SCOPE = 'https://www.googleapis.com/auth/calendar'
 CALENDAR_READONLY_SCOPE = 'https://www.googleapis.com/auth/calendar.readonly'
 CALENDAR_EVENTS_SCOPE = 'https://www.googleapis.com/auth/calendar.events'
 
 # Google Drive scopes
+DRIVE_SCOPE = 'https://www.googleapis.com/auth/drive'
 DRIVE_READONLY_SCOPE = 'https://www.googleapis.com/auth/drive.readonly'
 DRIVE_FILE_SCOPE = 'https://www.googleapis.com/auth/drive.file'
 
@@ -57,6 +60,7 @@ CUSTOM_SEARCH_SCOPE = 'https://www.googleapis.com/auth/cse'
 # Base OAuth scopes required for user identification
 BASE_SCOPES = [
     USERINFO_EMAIL_SCOPE,
+    USERINFO_PROFILE_SCOPE,
     OPENID_SCOPE
 ]
 
@@ -67,11 +71,13 @@ DOCS_SCOPES = [
 ]
 
 CALENDAR_SCOPES = [
+    CALENDAR_SCOPE,
     CALENDAR_READONLY_SCOPE,
     CALENDAR_EVENTS_SCOPE
 ]
 
 DRIVE_SCOPES = [
+    DRIVE_SCOPE,
     DRIVE_READONLY_SCOPE,
     DRIVE_FILE_SCOPE
 ]
diff --git a/auth/service_decorator.py b/auth/service_decorator.py
index 91d10ed..7692a7e 100644
--- a/auth/service_decorator.py
+++ b/auth/service_decorator.py
@@ -19,6 +19,57 @@ from auth.scopes import (
     CUSTOM_SEARCH_SCOPE
 )
 
+# OAuth 2.1 integration is now handled by FastMCP auth
+OAUTH21_INTEGRATION_AVAILABLE = True
+
+
+# REMOVED: _extract_and_verify_bearer_token function
+# This functionality is now handled by AuthInfoMiddleware
+
+async def get_authenticated_google_service_oauth21(
+    service_name: str,
+    version: str,
+    tool_name: str,
+    user_google_email: str,
+    required_scopes: List[str],
+    session_id: Optional[str] = None,
+    auth_token_email: Optional[str] = None,
+    allow_recent_auth: bool = False,
+) -> tuple[Any, str]:
+    """
+    OAuth 2.1 authentication using the session store with security validation.
+    """
+    from auth.oauth21_session_store import get_oauth21_session_store
+    from googleapiclient.discovery import build
+
+    store = get_oauth21_session_store()
+
+    # Use the new validation method to ensure session can only access its own credentials
+    credentials = store.get_credentials_with_validation(
+        requested_user_email=user_google_email,
+        session_id=session_id,
+        auth_token_email=auth_token_email,
+        allow_recent_auth=allow_recent_auth
+    )
+
+    if not credentials:
+        from auth.google_auth import GoogleAuthenticationError
+        raise GoogleAuthenticationError(
+            f"Access denied: Cannot retrieve credentials for {user_google_email}. "
+            f"You can only access credentials for your authenticated account."
+        )
+
+    # Check scopes
+    if not all(scope in credentials.scopes for scope in required_scopes):
+        from auth.google_auth import GoogleAuthenticationError
+        raise GoogleAuthenticationError(f"OAuth 2.1 credentials lack required scopes. Need: {required_scopes}, Have: {credentials.scopes}")
+
+    # Build service
+    service = build(service_name, version, credentials=credentials)
+    logger.info(f"[{tool_name}] Successfully authenticated {service_name} service using OAuth 2.1 for user: {user_google_email}")
+
+    return service, user_google_email
+
 logger = logging.getLogger(__name__)
 
 # Service configuration mapping
@@ -78,7 +129,7 @@ SCOPE_GROUPS = {
     # Tasks scopes
     "tasks": TASKS_SCOPE,
     "tasks_read": TASKS_READONLY_SCOPE,
-    
+
     # Custom Search scope
     "customsearch": CUSTOM_SEARCH_SCOPE,
 }
@@ -256,18 +307,73 @@ def require_google_service(
             if service is None:
                 try:
                     tool_name = func.__name__
-                    service, actual_user_email = await get_authenticated_google_service(
-                        service_name=service_name,
-                        version=service_version,
-                        tool_name=tool_name,
-                        user_google_email=user_google_email,
-                        required_scopes=resolved_scopes,
-                    )
+
+                    # SIMPLIFIED: Just get the authenticated user from the context
+                    # The AuthInfoMiddleware has already done all the authentication checks
+                    authenticated_user = None
+                    auth_method = None
+                    mcp_session_id = None
+
+                    try:
+                        from fastmcp.server.dependencies import get_context
+                        ctx = get_context()
+                        if ctx:
+                            # Get the authenticated user email set by AuthInfoMiddleware
+                            authenticated_user = ctx.get_state("authenticated_user_email")
+                            auth_method = ctx.get_state("authenticated_via")
+
+                            # Get session ID for logging
+                            if hasattr(ctx, 'session_id'):
+                                mcp_session_id = ctx.session_id
+                                # Set FastMCP session ID in context variable for propagation
+                                from core.context import set_fastmcp_session_id
+                                set_fastmcp_session_id(mcp_session_id)
+
+                            logger.info(f"[{tool_name}] Authentication from middleware: user={authenticated_user}, method={auth_method}")
+                    except Exception as e:
+                        logger.debug(f"[{tool_name}] Could not get FastMCP context: {e}")
+
+                    # Log authentication status
+                    logger.info(f"[{tool_name}] Authentication Status:"
+                              f" Method={auth_method or 'none'},"
+                              f" User={authenticated_user or 'none'},"
+                              f" MCPSessionID={mcp_session_id or 'none'}")
+
+                    from auth.oauth21_integration import is_oauth21_enabled
+
+                    if is_oauth21_enabled():
+                        logger.debug(f"[{tool_name}] Attempting OAuth 2.1 authentication flow.")
+                        # The downstream get_authenticated_google_service_oauth21 will handle
+                        # whether the user's token is valid for the requested resource.
+                        # This decorator should not block the call here.
+                        service, actual_user_email = await get_authenticated_google_service_oauth21(
+                            service_name=service_name,
+                            version=service_version,
+                            tool_name=tool_name,
+                            user_google_email=user_google_email,
+                            required_scopes=resolved_scopes,
+                            session_id=mcp_session_id,
+                            auth_token_email=authenticated_user,
+                            allow_recent_auth=False,
+                        )
+                    else:
+                        # If OAuth 2.1 is not enabled, always use the legacy authentication method.
+                        logger.debug(f"[{tool_name}] Using legacy authentication flow (OAuth 2.1 disabled).")
+                        service, actual_user_email = await get_authenticated_google_service(
+                            service_name=service_name,
+                            version=service_version,
+                            tool_name=tool_name,
+                            user_google_email=user_google_email,
+                            required_scopes=resolved_scopes,
+                            session_id=mcp_session_id,
+                        )
+
                     if cache_enabled:
                         cache_key = _get_cache_key(user_google_email, service_name, service_version, resolved_scopes)
                         _cache_service(cache_key, service, actual_user_email)
-                except GoogleAuthenticationError as e:
-                    raise Exception(str(e))
+                except GoogleAuthenticationError:
+                    # Re-raise the original error without wrapping it
+                    raise
 
             # --- Call the original function with the service object injected ---
             try:
@@ -340,19 +446,56 @@ def require_multiple_services(service_configs: List[Dict[str, Any]]):
 
                 try:
                     tool_name = func.__name__
-                    service, _ = await get_authenticated_google_service(
-                        service_name=service_name,
-                        version=service_version,
-                        tool_name=tool_name,
-                        user_google_email=user_google_email,
-                        required_scopes=resolved_scopes,
-                    )
+
+                    # SIMPLIFIED: Get authentication state from context (set by AuthInfoMiddleware)
+                    authenticated_user = None
+                    auth_method = None
+                    mcp_session_id = None
+
+                    try:
+                        from fastmcp.server.dependencies import get_context
+                        ctx = get_context()
+                        if ctx:
+                            authenticated_user = ctx.get_state("authenticated_user_email")
+                            auth_method = ctx.get_state("authenticated_via")
+                            if hasattr(ctx, 'session_id'):
+                                mcp_session_id = ctx.session_id
+                    except Exception as e:
+                        logger.debug(f"[{tool_name}] Could not get FastMCP context: {e}")
+
+                    # Use the same logic as single service decorator
+                    from auth.oauth21_integration import is_oauth21_enabled
+
+                    if is_oauth21_enabled():
+                        logger.debug(f"[{tool_name}] Attempting OAuth 2.1 authentication flow for {service_type}.")
+                        service, _ = await get_authenticated_google_service_oauth21(
+                            service_name=service_name,
+                            version=service_version,
+                            tool_name=tool_name,
+                            user_google_email=user_google_email,
+                            required_scopes=resolved_scopes,
+                            session_id=mcp_session_id,
+                            auth_token_email=authenticated_user,
+                            allow_recent_auth=False,
+                        )
+                    else:
+                        # If OAuth 2.1 is not enabled, always use the legacy authentication method.
+                        logger.debug(f"[{tool_name}] Using legacy authentication flow for {service_type} (OAuth 2.1 disabled).")
+                        service, _ = await get_authenticated_google_service(
+                            service_name=service_name,
+                            version=service_version,
+                            tool_name=tool_name,
+                            user_google_email=user_google_email,
+                            required_scopes=resolved_scopes,
+                            session_id=mcp_session_id,
+                        )
 
                     # Inject service with specified parameter name
                     kwargs[param_name] = service
 
-                except GoogleAuthenticationError as e:
-                    raise Exception(str(e))
+                except GoogleAuthenticationError:
+                    # Re-raise the original error without wrapping it
+                    raise
 
             # Call the original function with refresh error handling
             try:
diff --git a/auth/service_decorator_oauth21.py.bak b/auth/service_decorator_oauth21.py.bak
new file mode 100644
index 0000000..c4b16a8
--- /dev/null
+++ b/auth/service_decorator_oauth21.py.bak
@@ -0,0 +1,216 @@
+"""
+Enhanced Service Decorator with OAuth 2.1 Support
+
+This module provides an enhanced version of the service decorator that can
+extract and use OAuth 2.1 session context from FastMCP.
+"""
+
+import inspect
+import logging
+from functools import wraps
+from typing import Dict, List, Optional, Any, Callable, Union
+from datetime import datetime, timedelta
+
+from google.auth.exceptions import RefreshError
+
+from auth.service_decorator import (
+    SERVICE_CONFIGS,
+    SCOPE_GROUPS,
+    _resolve_scopes,
+    _get_cache_key,
+    _is_cache_valid,
+    _handle_token_refresh_error,
+    _get_cached_service,
+    _cache_service,
+    GoogleAuthenticationError,
+)
+from auth.oauth21_integration import get_authenticated_google_service_oauth21
+
+logger = logging.getLogger(__name__)
+
+
+def _extract_context_from_args(args: tuple, kwargs: dict, sig: inspect.Signature) -> Optional[Dict[str, Any]]:
+    """
+    Extract FastMCP Context from function arguments.
+    
+    Args:
+        args: Positional arguments
+        kwargs: Keyword arguments
+        sig: Function signature
+        
+    Returns:
+        Context information if found
+    """
+    param_names = list(sig.parameters.keys())
+    
+    # Check for Context type annotation
+    for param_name, param in sig.parameters.items():
+        if param.annotation and "Context" in str(param.annotation):
+            # Found Context parameter
+            if param_name in kwargs:
+                ctx = kwargs[param_name]
+            else:
+                try:
+                    param_index = param_names.index(param_name)
+                    if param_index < len(args):
+                        ctx = args[param_index]
+                    else:
+                        continue
+                except ValueError:
+                    continue
+            
+            # Extract relevant information from Context
+            if ctx:
+                context_info = {}
+                
+                # Try to get session_id
+                if hasattr(ctx, "session_id"):
+                    context_info["session_id"] = ctx.session_id
+                
+                # Try to get request object
+                if hasattr(ctx, "request"):
+                    context_info["request"] = ctx.request
+                    
+                # Try to get auth context from request state
+                if hasattr(ctx, "request") and hasattr(ctx.request, "state"):
+                    if hasattr(ctx.request.state, "auth"):
+                        context_info["auth_context"] = ctx.request.state.auth
+                
+                return context_info if context_info else None
+    
+    return None
+
+
+def require_google_service_oauth21(
+    service_type: str,
+    scopes: Union[str, List[str]],
+    version: Optional[str] = None,
+    cache_enabled: bool = True,
+    fallback_to_legacy: bool = True
+):
+    """
+    Enhanced decorator that injects authenticated Google service with OAuth 2.1 support.
+    
+    This decorator checks for FastMCP Context in the function parameters and uses
+    OAuth 2.1 session information if available, otherwise falls back to legacy auth.
+    
+    Args:
+        service_type: Type of Google service (e.g., 'gmail', 'drive')
+        scopes: Required scopes or scope aliases
+        version: API version (optional, uses default if not specified)
+        cache_enabled: Whether to cache service instances
+        fallback_to_legacy: Whether to fall back to legacy auth if OAuth 2.1 fails
+        
+    Usage:
+        @require_google_service_oauth21("gmail", "gmail_read")
+        async def search_emails(service, user_google_email: str, ctx: Context):
+            # service is automatically injected
+            # ctx provides OAuth 2.1 session context
+    """
+    def decorator(func: Callable) -> Callable:
+        # Get service configuration
+        if service_type not in SERVICE_CONFIGS:
+            raise ValueError(f"Unknown service type: {service_type}")
+        
+        service_config = SERVICE_CONFIGS[service_type]
+        service_name = service_config["service"]
+        service_version = version or service_config["version"]
+        
+        # Resolve scopes
+        resolved_scopes = _resolve_scopes(scopes)
+        
+        # Create wrapper with modified signature
+        sig = inspect.signature(func)
+        params = list(sig.parameters.values())
+        
+        # Remove 'service' parameter from signature
+        wrapper_params = [p for p in params if p.name != 'service']
+        wrapper_sig = sig.replace(parameters=wrapper_params)
+        
+        @wraps(func)
+        async def wrapper(*args, **kwargs):
+            # Extract user_google_email
+            user_google_email = None
+            if 'user_google_email' in kwargs:
+                user_google_email = kwargs['user_google_email']
+            else:
+                param_names = list(sig.parameters.keys())
+                try:
+                    user_email_index = param_names.index('user_google_email')
+                    if user_email_index < len(args):
+                        user_google_email = args[user_email_index]
+                except ValueError:
+                    pass
+            
+            if not user_google_email:
+                raise ValueError("user_google_email parameter is required")
+            
+            # Extract context information
+            context = _extract_context_from_args(args, kwargs, sig)
+            
+            service = None
+            actual_user_email = user_google_email
+            
+            # Check cache if enabled
+            if cache_enabled:
+                cache_key = _get_cache_key(user_google_email, service_name, service_version, resolved_scopes)
+                cached_result = _get_cached_service(cache_key)
+                if cached_result:
+                    service, actual_user_email = cached_result
+                    logger.debug(f"Using cached service for {user_google_email}")
+            
+            if service is None:
+                try:
+                    tool_name = func.__name__
+                    
+                    # Try OAuth 2.1 authentication with context
+                    if context:
+                        logger.debug(f"Attempting OAuth 2.1 authentication for {tool_name}")
+                        service, actual_user_email = await get_authenticated_google_service_oauth21(
+                            service_name=service_name,
+                            version=service_version,
+                            tool_name=tool_name,
+                            user_google_email=user_google_email,
+                            required_scopes=resolved_scopes,
+                            context=context,
+                        )
+                    elif fallback_to_legacy:
+                        # Fall back to legacy authentication
+                        logger.debug(f"Using legacy authentication for {tool_name}")
+                        from auth.google_auth import get_authenticated_google_service
+                        service, actual_user_email = await get_authenticated_google_service(
+                            service_name=service_name,
+                            version=service_version,
+                            tool_name=tool_name,
+                            user_google_email=user_google_email,
+                            required_scopes=resolved_scopes,
+                        )
+                    else:
+                        raise GoogleAuthenticationError(
+                            "OAuth 2.1 context required but not found"
+                        )
+                    
+                    # Cache the service if enabled
+                    if cache_enabled and service:
+                        cache_key = _get_cache_key(user_google_email, service_name, service_version, resolved_scopes)
+                        _cache_service(cache_key, service, actual_user_email)
+                        
+                except GoogleAuthenticationError as e:
+                    raise Exception(str(e))
+            
+            # Call the original function with the service object injected
+            try:
+                return await func(service, *args, **kwargs)
+            except RefreshError as e:
+                error_message = _handle_token_refresh_error(e, actual_user_email, service_name)
+                raise Exception(error_message)
+        
+        # Set the wrapper's signature to the one without 'service'
+        wrapper.__signature__ = wrapper_sig
+        return wrapper
+    
+    return decorator
+
+
+# Alias for backward compatibility
+require_google_service = require_google_service_oauth21
\ No newline at end of file
diff --git a/core/config.py b/core/config.py
new file mode 100644
index 0000000..2e3985b
--- /dev/null
+++ b/core/config.py
@@ -0,0 +1,32 @@
+"""
+Shared configuration for Google Workspace MCP server.
+This module holds configuration values that need to be shared across modules
+to avoid circular imports.
+"""
+
+import os
+
+# Server configuration
+WORKSPACE_MCP_PORT = int(os.getenv("PORT", os.getenv("WORKSPACE_MCP_PORT", 8000)))
+WORKSPACE_MCP_BASE_URI = os.getenv("WORKSPACE_MCP_BASE_URI", "http://localhost")
+USER_GOOGLE_EMAIL = os.getenv("USER_GOOGLE_EMAIL", None)
+
+# Transport mode (will be set by main.py)
+_current_transport_mode = "stdio"  # Default to stdio
+
+
+def set_transport_mode(mode: str):
+    """Set the current transport mode for OAuth callback handling."""
+    global _current_transport_mode
+    _current_transport_mode = mode
+
+
+def get_transport_mode() -> str:
+    """Get the current transport mode."""
+    return _current_transport_mode
+
+
+def get_oauth_redirect_uri() -> str:
+    """Get OAuth redirect URI based on current configuration."""
+    # Use the standard OAuth callback path
+    return f"{WORKSPACE_MCP_BASE_URI}:{WORKSPACE_MCP_PORT}/oauth2callback"
\ No newline at end of file
diff --git a/core/context.py b/core/context.py
index f54fcc1..d3e0b14 100644
--- a/core/context.py
+++ b/core/context.py
@@ -7,6 +7,11 @@ _injected_oauth_credentials = contextvars.ContextVar(
     "injected_oauth_credentials", default=None
 )
 
+# Context variable to hold FastMCP session ID for the life of a single request.
+_fastmcp_session_id = contextvars.ContextVar(
+    "fastmcp_session_id", default=None
+)
+
 def get_injected_oauth_credentials():
     """
     Retrieve injected OAuth credentials for the current request context.
@@ -19,4 +24,18 @@ def set_injected_oauth_credentials(credentials: Optional[dict]):
     Set or clear the injected OAuth credentials for the current request context.
     This is called by the service decorator.
     """
-    _injected_oauth_credentials.set(credentials)
\ No newline at end of file
+    _injected_oauth_credentials.set(credentials)
+
+def get_fastmcp_session_id() -> Optional[str]:
+    """
+    Retrieve the FastMCP session ID for the current request context.
+    This is called by authentication layer to get the current session.
+    """
+    return _fastmcp_session_id.get()
+
+def set_fastmcp_session_id(session_id: Optional[str]):
+    """
+    Set or clear the FastMCP session ID for the current request context.
+    This is called when a FastMCP request starts.
+    """
+    _fastmcp_session_id.set(session_id)
\ No newline at end of file
diff --git a/core/server.py b/core/server.py
index 281fb28..9379b53 100644
--- a/core/server.py
+++ b/core/server.py
@@ -1,93 +1,128 @@
 import logging
 import os
-from typing import Optional
+from typing import Optional, Union
 from importlib import metadata
 
-from fastapi import Header
-from fastapi.responses import HTMLResponse
-
-
-from mcp.server.fastmcp import FastMCP
+from fastapi.responses import HTMLResponse, JSONResponse
+from starlette.applications import Starlette
 from starlette.requests import Request
+from starlette.middleware import Middleware
+from fastapi.middleware.cors import CORSMiddleware
 
+from fastmcp import FastMCP
+
+from auth.oauth21_session_store import get_oauth21_session_store, set_auth_provider
 from auth.google_auth import handle_auth_callback, start_auth_flow, check_client_secrets
-from auth.oauth_callback_server import get_oauth_redirect_uri, ensure_oauth_callback_available
+from auth.mcp_session_middleware import MCPSessionMiddleware
 from auth.oauth_responses import create_error_response, create_success_response, create_server_error_response
-
-# Import shared configuration
-from auth.scopes import (
-    SCOPES,
-    USERINFO_EMAIL_SCOPE,  # noqa: F401
-    OPENID_SCOPE,  # noqa: F401
-    CALENDAR_READONLY_SCOPE,  # noqa: F401
-    CALENDAR_EVENTS_SCOPE,  # noqa: F401
-    DRIVE_READONLY_SCOPE,  # noqa: F401
-    DRIVE_FILE_SCOPE,  # noqa: F401
-    GMAIL_READONLY_SCOPE,  # noqa: F401
-    GMAIL_SEND_SCOPE,  # noqa: F401
-    GMAIL_COMPOSE_SCOPE,  # noqa: F401
-    GMAIL_MODIFY_SCOPE,  # noqa: F401
-    GMAIL_LABELS_SCOPE,  # noqa: F401
-    BASE_SCOPES,  # noqa: F401
-    CALENDAR_SCOPES,  # noqa: F401
-    DRIVE_SCOPES,  # noqa: F401
-    GMAIL_SCOPES,  # noqa: F401
-    DOCS_READONLY_SCOPE,  # noqa: F401
-    DOCS_WRITE_SCOPE,  # noqa: F401
-    CHAT_READONLY_SCOPE,  # noqa: F401
-    CHAT_WRITE_SCOPE,  # noqa: F401
-    CHAT_SPACES_SCOPE,  # noqa: F401
-    CHAT_SCOPES,  # noqa: F401
-    SHEETS_READONLY_SCOPE,  # noqa: F401
-    SHEETS_WRITE_SCOPE,  # noqa: F401
-    SHEETS_SCOPES,  # noqa: F401
-    FORMS_BODY_SCOPE,  # noqa: F401
-    FORMS_BODY_READONLY_SCOPE,  # noqa: F401
-    FORMS_RESPONSES_READONLY_SCOPE,  # noqa: F401
-    FORMS_SCOPES,  # noqa: F401
-    SLIDES_SCOPE,  # noqa: F401
-    SLIDES_READONLY_SCOPE,  # noqa: F401
-    SLIDES_SCOPES,  # noqa: F401
-    TASKS_SCOPE,  # noqa: F401
-    TASKS_READONLY_SCOPE,  # noqa: F401
-    TASKS_SCOPES,  # noqa: F401
-    CUSTOM_SEARCH_SCOPE,  # noqa: F401
-    CUSTOM_SEARCH_SCOPES,  # noqa: F401
+from auth.auth_info_middleware import AuthInfoMiddleware
+from auth.fastmcp_google_auth import GoogleWorkspaceAuthProvider
+from auth.scopes import SCOPES
+from core.config import (
+    WORKSPACE_MCP_PORT,
+    WORKSPACE_MCP_BASE_URI,
+    USER_GOOGLE_EMAIL,
+    get_transport_mode,
+    set_transport_mode as _set_transport_mode,
+    get_oauth_redirect_uri as get_oauth_redirect_uri_for_current_mode,
 )
 
-# Configure logging
+# Try to import GoogleRemoteAuthProvider for FastMCP 2.11.1+
+try:
+    from auth.google_remote_auth_provider import GoogleRemoteAuthProvider
+    GOOGLE_REMOTE_AUTH_AVAILABLE = True
+except ImportError:
+    GOOGLE_REMOTE_AUTH_AVAILABLE = False
+    GoogleRemoteAuthProvider = None
+
 logging.basicConfig(level=logging.INFO)
 logger = logging.getLogger(__name__)
 
-WORKSPACE_MCP_PORT = int(os.getenv("PORT", os.getenv("WORKSPACE_MCP_PORT", 8000)))
-WORKSPACE_MCP_BASE_URI = os.getenv("WORKSPACE_MCP_BASE_URI", "http://localhost")
-USER_GOOGLE_EMAIL = os.getenv("USER_GOOGLE_EMAIL", None)
-
-# Transport mode detection (will be set by main.py)
-_current_transport_mode = "stdio"  # Default to stdio
+_auth_provider: Optional[Union[GoogleWorkspaceAuthProvider, GoogleRemoteAuthProvider]] = None
 
-# Basic MCP server instance
-server = FastMCP(
+# --- Middleware Definitions ---
+cors_middleware = Middleware(
+    CORSMiddleware,
+    allow_origins=["*"],
+    allow_credentials=True,
+    allow_methods=["*"],
+    allow_headers=["*"],
+)
+session_middleware = Middleware(MCPSessionMiddleware)
+
+# Custom FastMCP that adds CORS to streamable HTTP
+class CORSEnabledFastMCP(FastMCP):
+    def streamable_http_app(self) -> "Starlette":
+        """Override to add CORS and session middleware to the app."""
+        app = super().streamable_http_app()
+        # Add session middleware first (to set context before other middleware)
+        app.user_middleware.insert(0, session_middleware)
+        # Add CORS as the second middleware
+        app.user_middleware.insert(1, cors_middleware)
+        # Rebuild middleware stack
+        app.middleware_stack = app.build_middleware_stack()
+        logger.info("Added session and CORS middleware to streamable HTTP app")
+        return app
+
+# --- Server Instance ---
+server = CORSEnabledFastMCP(
     name="google_workspace",
     port=WORKSPACE_MCP_PORT,
-    host="0.0.0.0"
+    host="0.0.0.0",
+    auth=None,
 )
 
+# Add the AuthInfo middleware to inject authentication into FastMCP context
+auth_info_middleware = AuthInfoMiddleware()
+server.add_middleware(auth_info_middleware)
+
+
 def set_transport_mode(mode: str):
-    """Set the current transport mode for OAuth callback handling."""
-    global _current_transport_mode
-    _current_transport_mode = mode
+    """Sets the transport mode for the server."""
+    _set_transport_mode(mode)
     logger.info(f"Transport mode set to: {mode}")
 
-def get_oauth_redirect_uri_for_current_mode() -> str:
-    """Get OAuth redirect URI based on current transport mode."""
-    return get_oauth_redirect_uri(WORKSPACE_MCP_PORT, WORKSPACE_MCP_BASE_URI)
+def configure_server_for_http():
+    """
+    Configures the authentication provider for HTTP transport.
+    This must be called BEFORE server.run().
+    """
+    global _auth_provider
+    transport_mode = get_transport_mode()
+
+    if transport_mode != "streamable-http":
+        return
+
+    oauth21_enabled = os.getenv("MCP_ENABLE_OAUTH21", "false").lower() == "true"
+
+    if oauth21_enabled:
+        if not os.getenv("GOOGLE_OAUTH_CLIENT_ID"):
+            logger.warning("OAuth 2.1 is enabled, but GOOGLE_OAUTH_CLIENT_ID is not set.")
+            return
+
+        if GOOGLE_REMOTE_AUTH_AVAILABLE:
+            logger.info("OAuth 2.1 is ENABLED. Initializing and attaching GoogleRemoteAuthProvider.")
+            try:
+                _auth_provider = GoogleRemoteAuthProvider()
+                server.auth = _auth_provider
+                set_auth_provider(_auth_provider)
+                from auth.oauth21_integration import enable_oauth21
+                enable_oauth21()
+            except Exception as e:
+                logger.error(f"Failed to initialize GoogleRemoteAuthProvider: {e}", exc_info=True)
+        else:
+            logger.error("OAuth 2.1 is enabled, but GoogleRemoteAuthProvider is not available.")
+    else:
+        logger.info("OAuth 2.1 is DISABLED. Server will use legacy tool-based authentication.")
+        server.auth = None
+
+def get_auth_provider() -> Optional[Union[GoogleWorkspaceAuthProvider, GoogleRemoteAuthProvider]]:
+    """Gets the global authentication provider instance."""
+    return _auth_provider
 
-# Health check endpoint
+# --- Custom Routes ---
 @server.custom_route("/health", methods=["GET"])
 async def health_check(request: Request):
-    """Health check endpoint for container orchestration."""
-    from fastapi.responses import JSONResponse
     try:
         version = metadata.version("workspace-mcp")
     except metadata.PackageNotFoundError:
@@ -96,112 +131,109 @@ async def health_check(request: Request):
         "status": "healthy",
         "service": "workspace-mcp",
         "version": version,
-        "transport": _current_transport_mode
+        "transport": get_transport_mode()
     })
 
-
 @server.custom_route("/oauth2callback", methods=["GET"])
 async def oauth2_callback(request: Request) -> HTMLResponse:
-    """
-    Handle OAuth2 callback from Google via a custom route.
-    This endpoint exchanges the authorization code for credentials and saves them.
-    It then displays a success or error page to the user.
-    """
     state = request.query_params.get("state")
     code = request.query_params.get("code")
     error = request.query_params.get("error")
 
     if error:
-        error_message = f"Authentication failed: Google returned an error: {error}. State: {state}."
-        logger.error(error_message)
-        return create_error_response(error_message)
+        msg = f"Authentication failed: Google returned an error: {error}. State: {state}."
+        logger.error(msg)
+        return create_error_response(msg)
 
     if not code:
-        error_message = "Authentication failed: No authorization code received from Google."
-        logger.error(error_message)
-        return create_error_response(error_message)
+        msg = "Authentication failed: No authorization code received from Google."
+        logger.error(msg)
+        return create_error_response(msg)
 
     try:
-        # Check if we have credentials available (environment variables or file)
         error_message = check_client_secrets()
         if error_message:
             return create_server_error_response(error_message)
 
-        logger.info(f"OAuth callback: Received code (state: {state}). Attempting to exchange for tokens.")
+        logger.info(f"OAuth callback: Received code (state: {state}).")
 
-        # Exchange code for credentials. handle_auth_callback will save them.
-        # The user_id returned here is the Google-verified email.
         verified_user_id, credentials = handle_auth_callback(
-            scopes=SCOPES, # Ensure all necessary scopes are requested
+            scopes=SCOPES,
             authorization_response=str(request.url),
             redirect_uri=get_oauth_redirect_uri_for_current_mode(),
-            session_id=None # Session ID tracking removed
+            session_id=None
         )
 
-        logger.info(f"OAuth callback: Successfully authenticated user: {verified_user_id} (state: {state}).")
+        logger.info(f"OAuth callback: Successfully authenticated user: {verified_user_id}.")
+
+        try:
+            store = get_oauth21_session_store()
+            mcp_session_id = None
+            if hasattr(request, 'state') and hasattr(request.state, 'session_id'):
+                mcp_session_id = request.state.session_id
+
+            store.store_session(
+                user_email=verified_user_id,
+                access_token=credentials.token,
+                refresh_token=credentials.refresh_token,
+                token_uri=credentials.token_uri,
+                client_id=credentials.client_id,
+                client_secret=credentials.client_secret,
+                scopes=credentials.scopes,
+                expiry=credentials.expiry,
+                session_id=f"google-{state}",
+                mcp_session_id=mcp_session_id,
+            )
+            logger.info(f"Stored Google credentials in OAuth 2.1 session store for {verified_user_id}")
+        except Exception as e:
+            logger.error(f"Failed to store credentials in OAuth 2.1 store: {e}")
 
-        # Return success page using shared template
         return create_success_response(verified_user_id)
-
     except Exception as e:
-        error_message_detail = f"Error processing OAuth callback (state: {state}): {str(e)}"
-        logger.error(error_message_detail, exc_info=True)
-        # Generic error page for any other issues during token exchange or credential saving
+        logger.error(f"Error processing OAuth callback: {str(e)}", exc_info=True)
         return create_server_error_response(str(e))
 
+# --- Tools ---
 @server.tool()
-async def start_google_auth(
-    service_name: str,
-    user_google_email: str = USER_GOOGLE_EMAIL
-) -> str:
-    """
-    Initiates the Google OAuth 2.0 authentication flow for the specified user email and service.
-    This is the primary method to establish credentials when no valid session exists or when targeting a specific account for a particular service.
-    It generates an authorization URL that the LLM must present to the user.
-    This initiates a new authentication flow for the specified user and service.
-
-    LLM Guidance:
-    - Use this tool when you need to authenticate a user for a specific Google service (e.g., "Google Calendar", "Google Docs", "Gmail", "Google Drive")
-      and don't have existing valid credentials for the session or specified email.
-    - You MUST provide the `user_google_email` and the `service_name`. If you don't know the email, ask the user first.
-    - Valid `service_name` values typically include "Google Calendar", "Google Docs", "Gmail", "Google Drive".
-    - After calling this tool, present the returned authorization URL clearly to the user and instruct them to:
-        1. Click the link and complete the sign-in/consent process in their browser.
-        2. Note the authenticated email displayed on the success page.
-        3. Provide that email back to you (the LLM).
-        4. Retry their original request, including the confirmed `user_google_email`.
-
-    Args:
-        user_google_email (str): The user's full Google email address (e.g., 'example@gmail.com'). This is REQUIRED.
-        service_name (str): The name of the Google service for which authentication is being requested (e.g., "Google Calendar", "Google Docs"). This is REQUIRED.
-
-    Returns:
-        str: A detailed message for the LLM with the authorization URL and instructions to guide the user through the authentication process.
-    """
-    if not user_google_email or not isinstance(user_google_email, str) or '@' not in user_google_email:
-        error_msg = "Invalid or missing 'user_google_email'. This parameter is required and must be a valid email address. LLM, please ask the user for their Google email address."
-        logger.error(f"[start_google_auth] {error_msg}")
-        raise Exception(error_msg)
-
-    if not service_name or not isinstance(service_name, str):
-        error_msg = "Invalid or missing 'service_name'. This parameter is required (e.g., 'Google Calendar', 'Google Docs'). LLM, please specify the service name."
-        logger.error(f"[start_google_auth] {error_msg}")
-        raise Exception(error_msg)
-
-    logger.info(f"Tool 'start_google_auth' invoked for user_google_email: '{user_google_email}', service: '{service_name}'.")
-
-    # Ensure OAuth callback is available for current transport mode
-    redirect_uri = get_oauth_redirect_uri_for_current_mode()
-    success, error_msg = ensure_oauth_callback_available(_current_transport_mode, WORKSPACE_MCP_PORT, WORKSPACE_MCP_BASE_URI)
-    if not success:
-        if error_msg:
-            raise Exception(f"Failed to start OAuth callback server: {error_msg}")
-        else:
-            raise Exception("Failed to start OAuth callback server. Please try again.")
+async def start_google_auth(service_name: str, user_google_email: str = USER_GOOGLE_EMAIL) -> str:
+    if not user_google_email:
+        raise ValueError("user_google_email must be provided.")
 
-    auth_result = await start_auth_flow(
-        user_google_email=user_google_email,
-        service_name=service_name,
-        redirect_uri=redirect_uri
+    error_message = check_client_secrets()
+    if error_message:
+        return f"**Authentication Error:** {error_message}"
+
+    try:
+        auth_url, _ = start_auth_flow(
+            scopes=SCOPES,
+            redirect_uri=get_oauth_redirect_uri_for_current_mode(),
+            login_hint=user_google_email
+        )
+        return (
+            "**Action Required: Authenticate with Google**\n\n"
+            "Please visit this URL to authenticate:\n\n"
+            f"**[Authenticate with Google]({auth_url})**\n\n"
+            "After authenticating, retry your request."
+        )
+    except Exception as e:
+        logger.error(f"Failed to start Google authentication flow: {e}", exc_info=True)
+        return f"**Error:** An unexpected error occurred: {e}"
+
+# OAuth 2.1 Discovery Endpoints - register manually when OAuth 2.1 is enabled but GoogleRemoteAuthProvider is not available
+# These will only be registered if MCP_ENABLE_OAUTH21=true and we're in fallback mode
+if os.getenv("MCP_ENABLE_OAUTH21", "false").lower() == "true" and not GOOGLE_REMOTE_AUTH_AVAILABLE:
+    from auth.oauth_common_handlers import (
+        handle_oauth_authorize,
+        handle_proxy_token_exchange,
+        handle_oauth_protected_resource,
+        handle_oauth_authorization_server,
+        handle_oauth_client_config,
+        handle_oauth_register
     )
-    return auth_result
+    
+    server.custom_route("/.well-known/oauth-protected-resource", methods=["GET", "OPTIONS"])(handle_oauth_protected_resource)
+    server.custom_route("/.well-known/oauth-authorization-server", methods=["GET", "OPTIONS"])(handle_oauth_authorization_server)
+    server.custom_route("/.well-known/oauth-client", methods=["GET", "OPTIONS"])(handle_oauth_client_config)
+    server.custom_route("/oauth2/authorize", methods=["GET", "OPTIONS"])(handle_oauth_authorize)
+    server.custom_route("/oauth2/token", methods=["POST", "OPTIONS"])(handle_proxy_token_exchange)
+    server.custom_route("/oauth2/register", methods=["POST", "OPTIONS"])(handle_oauth_register)
diff --git a/core/utils.py b/core/utils.py
index 9505fb6..36fb154 100644
--- a/core/utils.py
+++ b/core/utils.py
@@ -11,6 +11,7 @@ from typing import List, Optional
 
 from googleapiclient.errors import HttpError
 from .api_enablement import get_api_enablement_message
+from auth.google_auth import GoogleAuthenticationError
 
 logger = logging.getLogger(__name__)
 
@@ -306,6 +307,9 @@ def handle_http_errors(tool_name: str, is_read_only: bool = False, service_type:
                 except TransientNetworkError:
                     # Re-raise without wrapping to preserve the specific error type
                     raise
+                except GoogleAuthenticationError:
+                    # Re-raise authentication errors without wrapping
+                    raise
                 except Exception as e:
                     message = f"An unexpected error occurred in {tool_name}: {e}"
                     logger.exception(message)
diff --git a/gdocs/docs_tools.py b/gdocs/docs_tools.py
index f868f72..822cf4f 100644
--- a/gdocs/docs_tools.py
+++ b/gdocs/docs_tools.py
@@ -101,7 +101,7 @@ async def get_doc_content(
         )
         # Tab header format constant
         TAB_HEADER_FORMAT = "\n--- TAB: {tab_name} ---\n"
-        
+
         def extract_text_from_elements(elements, tab_name=None, depth=0):
             """Extract text from document elements (paragraphs, tables, etc.)"""
             # Prevent infinite recursion by limiting depth
diff --git a/gmail/gmail_tools.py b/gmail/gmail_tools.py
index 0e634b9..108b99f 100644
--- a/gmail/gmail_tools.py
+++ b/gmail/gmail_tools.py
@@ -16,12 +16,12 @@ from fastapi import Body
 
 from auth.service_decorator import require_google_service
 from core.utils import handle_http_errors
-from core.server import (
+from core.server import server
+from auth.scopes import (
     GMAIL_SEND_SCOPE,
     GMAIL_COMPOSE_SCOPE,
     GMAIL_MODIFY_SCOPE,
     GMAIL_LABELS_SCOPE,
-    server,
 )
 
 logger = logging.getLogger(__name__)
diff --git a/gsearch/search_tools.py b/gsearch/search_tools.py
index ef17e28..fd993d3 100644
--- a/gsearch/search_tools.py
+++ b/gsearch/search_tools.py
@@ -7,7 +7,7 @@ This module provides MCP tools for interacting with Google Programmable Search E
 import logging
 import asyncio
 import os
-from typing import Optional, List, Dict, Any, Literal
+from typing import Optional, List, Literal
 
 from auth.service_decorator import require_google_service
 from core.server import server
@@ -208,7 +208,7 @@ async def get_search_engine_info(
     search_info = result.get('searchInformation', {})
     if search_info:
         total_results = search_info.get('totalResults', 'Unknown')
-        confirmation_message += f"\nSearch Statistics:\n"
+        confirmation_message += "\nSearch Statistics:\n"
         confirmation_message += f"  - Total indexed results: {total_results}\n"
 
     logger.info(f"Search engine info retrieved successfully for {user_google_email}")
diff --git a/main.py b/main.py
index 7ac1fec..d1c5889 100644
--- a/main.py
+++ b/main.py
@@ -3,11 +3,15 @@ import logging
 import os
 import sys
 from importlib import metadata
-
-# Local imports
-from core.server import server, set_transport_mode
+from dotenv import load_dotenv
+from core.server import server, set_transport_mode, configure_server_for_http
 from core.utils import check_credentials_directory_permissions
 
+# Load environment variables from .env file, specifying an explicit path
+# This prevents accidentally loading a .env file from a different directory
+dotenv_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), '.env')
+load_dotenv(dotenv_path=dotenv_path)
+
 logging.basicConfig(
     level=logging.INFO,
     format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
@@ -82,6 +86,28 @@ def main():
     safe_print(f"   🐍 Python: {sys.version.split()[0]}")
     safe_print("")
 
+    # Active Configuration
+    safe_print("엑 Active Configuration:")
+
+    # Redact client secret for security
+    client_secret = os.getenv('GOOGLE_OAUTH_CLIENT_SECRET', 'Not Set')
+    redacted_secret = f"{client_secret[:4]}...{client_secret[-4:]}" if len(client_secret) > 8 else "Invalid or too short"
+
+    config_vars = {
+        "GOOGLE_OAUTH_CLIENT_ID": os.getenv('GOOGLE_OAUTH_CLIENT_ID', 'Not Set'),
+        "GOOGLE_OAUTH_CLIENT_SECRET": redacted_secret,
+        "USER_GOOGLE_EMAIL": os.getenv('USER_GOOGLE_EMAIL', 'Not Set'),
+        "MCP_SINGLE_USER_MODE": os.getenv('MCP_SINGLE_USER_MODE', 'false'),
+        "MCP_ENABLE_OAUTH21": os.getenv('MCP_ENABLE_OAUTH21', 'false'),
+        "OAUTHLIB_INSECURE_TRANSPORT": os.getenv('OAUTHLIB_INSECURE_TRANSPORT', 'false'),
+        "GOOGLE_CLIENT_SECRET_PATH": os.getenv('GOOGLE_CLIENT_SECRET_PATH', 'Not Set'),
+    }
+
+    for key, value in config_vars.items():
+        safe_print(f"   - {key}: {value}")
+    safe_print("")
+
+
     # Import tool modules to register them with the MCP server via decorators
     tool_imports = {
         'gmail': lambda: __import__('gmail.gmail_tools'),
@@ -145,6 +171,13 @@ def main():
         # Set transport mode for OAuth callback handling
         set_transport_mode(args.transport)
 
+        # Configure auth initialization for FastMCP lifecycle events
+        if args.transport == 'streamable-http':
+            configure_server_for_http()
+            safe_print("🔐 HTTP server configured for authentication.")
+        else:
+            safe_print("🔐 Using legacy authentication for stdio mode.")
+
         if args.transport == 'streamable-http':
             safe_print(f"🚀 Starting server on {base_uri}:{port}")
         else:
@@ -155,7 +188,7 @@ def main():
             if success:
                 safe_print(f"   OAuth callback server started on {base_uri}:{port}/oauth2callback")
             else:
-                warning_msg = f"   ⚠️  Warning: Failed to start OAuth callback server"
+                warning_msg = "   ⚠️  Warning: Failed to start OAuth callback server"
                 if error_msg:
                     warning_msg += f": {error_msg}"
                 safe_print(warning_msg)
@@ -164,7 +197,7 @@ def main():
         safe_print("")
 
         if args.transport == 'streamable-http':
-            # The server is already configured with port and server_url in core/server.py
+            # The server has CORS middleware built-in via CORSEnabledFastMCP
             server.run(transport="streamable-http")
         else:
             server.run()
diff --git a/pyproject.toml b/pyproject.toml
index 4b9ab18..dea934d 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -11,7 +11,7 @@ keywords = [ "mcp", "google", "workspace", "llm", "ai", "claude", "model", "cont
 requires-python = ">=3.10"
 dependencies = [
  "fastapi>=0.115.12",
- "fastmcp>=2.10.6",
+ "fastmcp==2.11.1",
  "google-api-python-client>=2.168.0",
  "google-auth-httplib2>=0.2.0",
  "google-auth-oauthlib>=1.2.2",
@@ -19,6 +19,9 @@ dependencies = [
  "pyjwt>=2.10.1",
  "ruff>=0.12.4",
  "tomlkit",
+ "aiohttp>=3.9.0",
+ "cachetools>=5.3.0",
+ "cryptography>=41.0.0",
 ]
 classifiers = [
     "Development Status :: 4 - Beta",
