2025-04-27 14:30:11 -04:00
import logging
2025-12-23 14:05:51 +01:00
import os
2025-10-05 18:00:10 -04:00
from typing import List , Optional
2025-06-14 13:15:36 -04:00
from importlib import metadata
2025-05-06 09:36:48 -04:00
2025-11-29 15:06:57 +01:00
from fastapi.responses import HTMLResponse , JSONResponse , FileResponse
2025-08-02 18:55:35 -04:00
from starlette.applications import Starlette
2025-05-22 17:02:00 -04:00
from starlette.requests import Request
2025-08-02 09:52:16 -04:00
from starlette.middleware import Middleware
2025-05-06 09:36:48 -04:00
2025-08-05 14:34:11 -04:00
from fastmcp import FastMCP
2025-10-05 18:00:10 -04:00
from fastmcp.server.auth.providers.google import GoogleProvider
2025-08-05 14:34:11 -04:00
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
2025-08-02 18:55:35 -04:00
from auth.mcp_session_middleware import MCPSessionMiddleware
2025-12-13 13:49:28 -08:00
from auth.oauth_responses import (
create_error_response ,
create_success_response ,
create_server_error_response ,
)
2025-08-05 10:22:01 -04:00
from auth.auth_info_middleware import AuthInfoMiddleware
2025-12-13 13:49:28 -08:00
from auth.scopes import SCOPES , get_current_scopes # noqa
2025-08-03 10:30:04 -04:00
from core.config import (
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 ,
2025-05-13 12:36:53 -04:00
)
2025-04-27 14:30:11 -04:00
logging . basicConfig ( level = logging . INFO )
logger = logging . getLogger ( __name__ )
2025-10-05 18:00:10 -04:00
_auth_provider : Optional [ GoogleProvider ] = None
_legacy_callback_registered = False
2025-08-02 09:52:16 -04:00
2025-08-02 15:40:23 -04:00
session_middleware = Middleware ( MCPSessionMiddleware )
2025-12-13 13:49:28 -08:00
2025-08-08 16:43:33 -04:00
# Custom FastMCP that adds secure middleware stack for OAuth 2.1
class SecureFastMCP ( FastMCP ):
2025-08-02 09:52:16 -04:00
def streamable_http_app ( self ) -> "Starlette" :
2025-08-08 16:43:33 -04:00
"""Override to add secure middleware stack for OAuth 2.1."""
2025-08-02 09:52:16 -04:00
app = super () . streamable_http_app ()
2025-08-09 10:46:31 -04:00
2025-08-08 16:43:33 -04:00
# Add middleware in order (first added = outermost layer)
2025-08-09 11:44:12 -04:00
# Session Management - extracts session info for MCP context
app . user_middleware . insert ( 0 , session_middleware )
2025-08-09 10:46:31 -04:00
2025-08-02 09:52:16 -04:00
# Rebuild middleware stack
app . middleware_stack = app . build_middleware_stack ()
2025-08-09 11:44:12 -04:00
logger . info ( "Added middleware stack: Session Management" )
2025-08-02 09:52:16 -04:00
return app
2025-12-13 13:49:28 -08:00
2025-08-08 16:43:33 -04:00
server = SecureFastMCP (
2025-08-09 10:46:31 -04:00
name = "google_workspace" ,
2025-08-05 14:34:11 -04:00
auth = None ,
2025-05-11 10:07:37 -04:00
)
2025-08-05 10:22:01 -04:00
# Add the AuthInfo middleware to inject authentication into FastMCP context
auth_info_middleware = AuthInfoMiddleware ()
server . add_middleware ( auth_info_middleware )
2025-08-03 12:00:41 -04:00
2025-12-27 12:23:49 -08:00
def _parse_bool_env ( value : str ) -> bool :
"""Parse environment variable string to boolean."""
return value . lower () in ( "1" , "true" , "yes" , "on" )
2025-06-07 16:00:55 -04:00
def set_transport_mode ( mode : str ):
2025-08-05 14:34:11 -04:00
"""Sets the transport mode for the server."""
2025-08-03 10:30:04 -04:00
_set_transport_mode ( mode )
2025-09-09 11:09:58 -04:00
logger . info ( f "Transport: { mode } " )
2025-06-07 16:00:55 -04:00
2025-10-05 18:00:10 -04:00
def _ensure_legacy_callback_route () -> None :
global _legacy_callback_registered
if _legacy_callback_registered :
return
server . custom_route ( "/oauth2callback" , methods = [ "GET" ])( legacy_oauth2_callback )
_legacy_callback_registered = True
2025-12-13 13:49:28 -08:00
2025-08-05 14:34:11 -04:00
def configure_server_for_http ():
"""
Configures the authentication provider for HTTP transport.
This must be called BEFORE server.run().
"""
2025-08-02 14:32:42 -04:00
global _auth_provider
2025-08-09 10:46:31 -04:00
2025-08-05 14:34:11 -04:00
transport_mode = get_transport_mode ()
2025-08-02 14:32:42 -04:00
2025-08-05 14:34:11 -04:00
if transport_mode != "streamable-http" :
return
2025-08-02 09:52:16 -04:00
2025-08-09 10:46:31 -04:00
# Use centralized OAuth configuration
from auth.oauth_config import get_oauth_config
2025-12-13 13:49:28 -08:00
2025-08-09 10:46:31 -04:00
config = get_oauth_config ()
2025-08-12 08:45:24 -04:00
2025-08-09 10:46:31 -04:00
# Check if OAuth 2.1 is enabled via centralized config
oauth21_enabled = config . is_oauth21_enabled ()
2025-08-02 09:52:16 -04:00
2025-08-05 14:34:11 -04:00
if oauth21_enabled :
2025-08-09 10:46:31 -04:00
if not config . is_configured ():
2025-09-09 11:13:30 -04:00
logger . warning ( "OAuth 2.1 enabled but OAuth credentials not configured" )
2025-08-05 14:34:11 -04:00
return
2025-08-03 12:15:44 -04:00
2025-12-27 12:20:26 -08:00
def validate_and_derive_jwt_key (
jwt_signing_key_override : str | None , client_secret : str
) -> bytes :
2025-12-27 12:16:58 -08:00
"""Validate JWT signing key override and derive the final JWT key."""
if jwt_signing_key_override :
if len ( jwt_signing_key_override ) < 12 :
logger . warning (
"OAuth 2.1: FASTMCP_SERVER_AUTH_GOOGLE_JWT_SIGNING_KEY is less than 12 characters; "
"use a longer secret to improve key derivation strength."
)
return derive_jwt_key (
low_entropy_material = jwt_signing_key_override ,
salt = "fastmcp-jwt-signing-key" ,
)
else :
return derive_jwt_key (
high_entropy_material = client_secret ,
salt = "fastmcp-jwt-signing-key" ,
)
2025-08-12 08:45:24 -04:00
try :
2025-12-27 12:23:49 -08:00
# Import common dependencies for storage backends
from key_value.aio.wrappers.encryption import FernetEncryptionWrapper
from cryptography.fernet import Fernet
from fastmcp.server.auth.jwt_issuer import derive_jwt_key
2025-10-05 18:00:10 -04:00
required_scopes : List [ str ] = sorted ( get_current_scopes ())
2025-10-24 15:43:29 +03:00
2025-12-23 14:05:51 +01:00
client_storage = None
jwt_signing_key_override = (
os . getenv ( "FASTMCP_SERVER_AUTH_GOOGLE_JWT_SIGNING_KEY" , "" ) . strip ()
or None
)
2025-12-27 12:08:09 -08:00
storage_backend = (
os . getenv ( "WORKSPACE_MCP_OAUTH_PROXY_STORAGE_BACKEND" , "" )
. strip ()
. lower ()
)
2025-12-23 14:05:51 +01:00
valkey_host = os . getenv ( "WORKSPACE_MCP_OAUTH_PROXY_VALKEY_HOST" , "" ) . strip ()
2025-12-27 12:06:20 -08:00
# Determine storage backend: valkey, disk, memory (default)
use_valkey = storage_backend == "valkey" or bool ( valkey_host )
use_disk = storage_backend == "disk"
2025-12-23 14:05:51 +01:00
if use_valkey :
try :
from key_value.aio.stores.valkey import ValkeyStore
valkey_port_raw = os . getenv (
"WORKSPACE_MCP_OAUTH_PROXY_VALKEY_PORT" , "6379"
) . strip ()
valkey_db_raw = os . getenv (
"WORKSPACE_MCP_OAUTH_PROXY_VALKEY_DB" , "0"
) . strip ()
valkey_port = int ( valkey_port_raw )
valkey_db = int ( valkey_db_raw )
valkey_use_tls_raw = os . getenv (
"WORKSPACE_MCP_OAUTH_PROXY_VALKEY_USE_TLS" , ""
) . strip ()
2025-12-27 12:23:49 -08:00
valkey_use_tls = (
2025-12-27 12:24:39 -08:00
_parse_bool_env ( valkey_use_tls_raw )
if valkey_use_tls_raw
2025-12-27 12:23:49 -08:00
else valkey_port == 6380
)
2025-12-23 14:05:51 +01:00
valkey_request_timeout_ms_raw = os . getenv (
"WORKSPACE_MCP_OAUTH_PROXY_VALKEY_REQUEST_TIMEOUT_MS" , ""
) . strip ()
valkey_connection_timeout_ms_raw = os . getenv (
"WORKSPACE_MCP_OAUTH_PROXY_VALKEY_CONNECTION_TIMEOUT_MS" , ""
) . strip ()
valkey_request_timeout_ms = (
int ( valkey_request_timeout_ms_raw )
if valkey_request_timeout_ms_raw
else None
)
valkey_connection_timeout_ms = (
int ( valkey_connection_timeout_ms_raw )
if valkey_connection_timeout_ms_raw
else None
)
valkey_username = (
os . getenv (
"WORKSPACE_MCP_OAUTH_PROXY_VALKEY_USERNAME" , ""
) . strip ()
or None
)
valkey_password = (
os . getenv (
"WORKSPACE_MCP_OAUTH_PROXY_VALKEY_PASSWORD" , ""
) . strip ()
or None
)
if not valkey_host :
valkey_host = "localhost"
client_storage = ValkeyStore (
host = valkey_host ,
port = valkey_port ,
db = valkey_db ,
username = valkey_username ,
password = valkey_password ,
)
# Configure TLS and timeouts on the underlying Glide client config.
# ValkeyStore currently doesn't expose these settings directly.
glide_config = getattr ( client_storage , "_client_config" , None )
if glide_config is not None :
glide_config . use_tls = valkey_use_tls
is_remote_host = valkey_host not in { "localhost" , "127.0.0.1" }
if valkey_request_timeout_ms is None and (
valkey_use_tls or is_remote_host
):
# Glide defaults to 250ms if unset; increase for remote/TLS endpoints.
valkey_request_timeout_ms = 5000
if valkey_request_timeout_ms is not None :
glide_config . request_timeout = valkey_request_timeout_ms
if valkey_connection_timeout_ms is None and (
valkey_use_tls or is_remote_host
):
valkey_connection_timeout_ms = 10000
if valkey_connection_timeout_ms is not None :
from glide_shared.config import (
AdvancedGlideClientConfiguration ,
)
glide_config . advanced_config = (
AdvancedGlideClientConfiguration (
connection_timeout = valkey_connection_timeout_ms
)
)
2025-12-27 12:20:26 -08:00
jwt_signing_key = validate_and_derive_jwt_key (
jwt_signing_key_override , config . client_secret
)
2025-12-23 14:05:51 +01:00
storage_encryption_key = derive_jwt_key (
high_entropy_material = jwt_signing_key . decode (),
salt = "fastmcp-storage-encryption-key" ,
)
client_storage = FernetEncryptionWrapper (
key_value = client_storage ,
fernet = Fernet ( key = storage_encryption_key ),
)
logger . info (
"OAuth 2.1: Using ValkeyStore for FastMCP OAuth proxy client_storage (host= %s , port= %s , db= %s , tls= %s )" ,
valkey_host ,
valkey_port ,
valkey_db ,
valkey_use_tls ,
)
if valkey_request_timeout_ms is not None :
logger . info (
"OAuth 2.1: Valkey request timeout set to %s ms" ,
valkey_request_timeout_ms ,
)
if valkey_connection_timeout_ms is not None :
logger . info (
"OAuth 2.1: Valkey connection timeout set to %s ms" ,
valkey_connection_timeout_ms ,
)
logger . info (
"OAuth 2.1: Applied Fernet encryption wrapper to Valkey client_storage (key derived from FASTMCP_SERVER_AUTH_GOOGLE_JWT_SIGNING_KEY or GOOGLE_OAUTH_CLIENT_SECRET)."
)
except ImportError as exc :
logger . warning (
"OAuth 2.1: Valkey client_storage requested but Valkey dependencies are not installed ( %s ). "
2025-12-28 12:52:40 -08:00
"Install 'workspace-mcp[valkey]' (or 'py-key-value-aio[valkey]', which includes 'valkey-glide') "
"or unset WORKSPACE_MCP_OAUTH_PROXY_STORAGE_BACKEND/WORKSPACE_MCP_OAUTH_PROXY_VALKEY_HOST." ,
2025-12-23 14:05:51 +01:00
exc ,
)
except ValueError as exc :
logger . warning (
"OAuth 2.1: Invalid Valkey configuration; falling back to default storage ( %s )." ,
exc ,
)
2025-12-27 12:06:20 -08:00
elif use_disk :
try :
from key_value.aio.stores.disk import DiskStore
disk_directory = os . getenv (
"WORKSPACE_MCP_OAUTH_PROXY_DISK_DIRECTORY" , ""
) . strip ()
if not disk_directory :
# Default to FASTMCP_HOME/oauth-proxy or ~/.fastmcp/oauth-proxy
fastmcp_home = os . getenv ( "FASTMCP_HOME" , "" ) . strip ()
if fastmcp_home :
disk_directory = os . path . join ( fastmcp_home , "oauth-proxy" )
else :
2025-12-27 12:08:09 -08:00
disk_directory = os . path . expanduser (
"~/.fastmcp/oauth-proxy"
)
2025-12-27 12:06:20 -08:00
client_storage = DiskStore ( directory = disk_directory )
2025-12-27 12:20:26 -08:00
jwt_signing_key = validate_and_derive_jwt_key (
jwt_signing_key_override , config . client_secret
)
2025-12-27 12:06:20 -08:00
storage_encryption_key = derive_jwt_key (
high_entropy_material = jwt_signing_key . decode (),
salt = "fastmcp-storage-encryption-key" ,
)
client_storage = FernetEncryptionWrapper (
key_value = client_storage ,
fernet = Fernet ( key = storage_encryption_key ),
)
logger . info (
"OAuth 2.1: Using DiskStore for FastMCP OAuth proxy client_storage (directory= %s )" ,
disk_directory ,
)
except ImportError as exc :
logger . warning (
"OAuth 2.1: Disk storage requested but dependencies not available ( %s ). "
"Falling back to default storage." ,
exc ,
)
elif storage_backend == "memory" :
from key_value.aio.stores.memory import MemoryStore
2025-12-27 12:08:09 -08:00
2025-12-27 12:06:20 -08:00
client_storage = MemoryStore ()
2025-12-27 12:08:09 -08:00
logger . info (
"OAuth 2.1: Using MemoryStore for FastMCP OAuth proxy client_storage"
)
2025-12-27 12:06:20 -08:00
# else: client_storage remains None, FastMCP uses its default
2025-12-23 14:05:51 +01:00
2025-12-27 12:23:49 -08:00
# Ensure JWT signing key is always derived for all storage backends
2025-12-27 12:24:39 -08:00
if "jwt_signing_key" not in locals ():
2025-12-27 12:23:49 -08:00
jwt_signing_key = validate_and_derive_jwt_key (
jwt_signing_key_override , config . client_secret
)
2025-10-24 15:43:29 +03:00
# Check if external OAuth provider is configured
if config . is_external_oauth21_provider ():
# External OAuth mode: use custom provider that handles ya29.* access tokens
from auth.external_oauth_provider import ExternalOAuthProvider
provider = ExternalOAuthProvider (
client_id = config . client_id ,
client_secret = config . client_secret ,
base_url = config . get_oauth_base_url (),
redirect_path = config . redirect_path ,
required_scopes = required_scopes ,
)
# Disable protocol-level auth, expect bearer tokens in tool calls
server . auth = None
2025-12-13 13:49:28 -08:00
logger . info (
"OAuth 2.1 enabled with EXTERNAL provider mode - protocol-level auth disabled"
)
logger . info (
"Expecting Authorization bearer tokens in tool call headers"
)
2025-10-24 15:43:29 +03:00
else :
# Standard OAuth 2.1 mode: use FastMCP's GoogleProvider
provider = GoogleProvider (
client_id = config . client_id ,
client_secret = config . client_secret ,
base_url = config . get_oauth_base_url (),
redirect_path = config . redirect_path ,
required_scopes = required_scopes ,
2025-12-23 14:05:51 +01:00
client_storage = client_storage ,
2025-12-27 12:23:49 -08:00
jwt_signing_key = jwt_signing_key ,
2025-10-24 15:43:29 +03:00
)
# Enable protocol-level auth
server . auth = provider
2025-12-13 13:49:28 -08:00
logger . info (
"OAuth 2.1 enabled using FastMCP GoogleProvider with protocol-level auth"
)
2025-10-24 15:43:29 +03:00
# Always set auth provider for token validation in middleware
2025-10-05 18:00:10 -04:00
set_auth_provider ( provider )
_auth_provider = provider
except Exception as exc :
2025-12-13 13:49:28 -08:00
logger . error (
"Failed to initialize FastMCP GoogleProvider: %s " , exc , exc_info = True
)
2025-08-12 08:45:24 -04:00
raise
2025-08-05 14:34:11 -04:00
else :
2025-08-09 10:46:31 -04:00
logger . info ( "OAuth 2.0 mode - Server will use legacy authentication." )
2025-08-05 14:34:11 -04:00
server . auth = None
2025-10-05 18:00:10 -04:00
_auth_provider = None
set_auth_provider ( None )
_ensure_legacy_callback_route ()
2025-07-19 19:33:55 -04:00
2025-10-05 18:00:10 -04:00
def get_auth_provider () -> Optional [ GoogleProvider ]:
2025-08-05 14:34:11 -04:00
"""Gets the global authentication provider instance."""
2025-08-02 14:32:42 -04:00
return _auth_provider
2025-07-19 19:33:55 -04:00
2025-12-13 13:49:28 -08:00
2025-06-07 18:49:54 -04:00
@server.custom_route ( "/health" , methods = [ "GET" ])
2025-06-08 14:23:28 -04:00
async def health_check ( request : Request ):
2025-06-14 13:15:36 -04:00
try :
2025-06-14 13:32:30 -04:00
version = metadata . version ( "workspace-mcp" )
2025-06-14 13:15:36 -04:00
except metadata . PackageNotFoundError :
version = "dev"
2025-12-13 13:49:28 -08:00
return JSONResponse (
{
"status" : "healthy" ,
"service" : "workspace-mcp" ,
"version" : version ,
"transport" : get_transport_mode (),
}
)
2025-06-07 18:49:54 -04:00
2025-11-29 15:06:57 +01:00
@server.custom_route ( "/attachments/ {file_id} " , methods = [ "GET" ])
2025-12-21 05:08:17 +00:00
async def serve_attachment ( file_id : str ):
2025-11-29 15:06:57 +01:00
"""Serve a stored attachment file."""
from core.attachment_storage import get_attachment_storage
2025-12-13 13:49:28 -08:00
2025-11-29 15:06:57 +01:00
storage = get_attachment_storage ()
metadata = storage . get_attachment_metadata ( file_id )
2025-12-13 13:49:28 -08:00
2025-11-29 15:06:57 +01:00
if not metadata :
return JSONResponse (
2025-12-13 13:49:28 -08:00
{ "error" : "Attachment not found or expired" }, status_code = 404
2025-11-29 15:06:57 +01:00
)
2025-12-13 13:49:28 -08:00
2025-11-29 15:06:57 +01:00
file_path = storage . get_attachment_path ( file_id )
if not file_path :
2025-12-13 13:49:28 -08:00
return JSONResponse ({ "error" : "Attachment file not found" }, status_code = 404 )
2025-11-29 15:06:57 +01:00
return FileResponse (
path = str ( file_path ),
filename = metadata [ "filename" ],
2025-12-13 13:49:28 -08:00
media_type = metadata [ "mime_type" ],
2025-11-29 15:06:57 +01:00
)
2025-12-13 13:49:28 -08:00
2025-10-05 18:00:10 -04:00
async def legacy_oauth2_callback ( request : Request ) -> HTMLResponse :
2025-05-11 15:37:44 -04:00
state = request . query_params . get ( "state" )
code = request . query_params . get ( "code" )
error = request . query_params . get ( "error" )
if error :
2025-12-13 13:49:28 -08:00
msg = (
f "Authentication failed: Google returned an error: { error } . State: { state } ."
)
2025-08-05 14:34:11 -04:00
logger . error ( msg )
return create_error_response ( msg )
2025-05-11 15:37:44 -04:00
if not code :
2025-08-05 14:34:11 -04:00
msg = "Authentication failed: No authorization code received from Google."
logger . error ( msg )
return create_error_response ( msg )
2025-05-11 10:07:37 -04:00
try :
2025-06-28 12:56:43 -07:00
error_message = check_client_secrets ()
if error_message :
return create_server_error_response ( error_message )
2025-05-11 15:37:44 -04:00
2025-08-05 14:34:11 -04:00
logger . info ( f "OAuth callback: Received code (state: { state } )." )
2025-05-13 12:36:53 -04:00
2025-09-28 16:08:41 -04:00
mcp_session_id = None
2025-12-13 13:49:28 -08:00
if hasattr ( request , "state" ) and hasattr ( request . state , "session_id" ):
2025-09-28 16:08:41 -04:00
mcp_session_id = request . state . session_id
2025-05-11 17:15:05 -04:00
verified_user_id , credentials = handle_auth_callback (
2025-08-24 14:00:21 -04:00
scopes = get_current_scopes (),
2025-05-11 17:15:05 -04:00
authorization_response = str ( request . url ),
2025-06-07 16:00:55 -04:00
redirect_uri = get_oauth_redirect_uri_for_current_mode (),
2025-12-13 13:49:28 -08:00
session_id = mcp_session_id ,
2025-05-11 10:07:37 -04:00
)
2025-05-13 12:36:53 -04:00
2025-12-13 13:49:28 -08:00
logger . info (
f "OAuth callback: Successfully authenticated user: { verified_user_id } ."
)
2025-08-02 14:32:42 -04:00
2025-08-02 10:38:11 -04:00
try :
store = get_oauth21_session_store ()
2025-08-05 14:34:11 -04:00
2025-08-02 10:38:11 -04:00
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 ,
2025-08-05 14:34:11 -04:00
session_id = f "google- { state } " ,
mcp_session_id = mcp_session_id ,
2025-08-02 10:38:11 -04:00
)
2025-12-13 13:49:28 -08:00
logger . info (
f "Stored Google credentials in OAuth 2.1 session store for { verified_user_id } "
)
2025-08-02 10:38:11 -04:00
except Exception as e :
2025-08-05 14:34:11 -04:00
logger . error ( f "Failed to store credentials in OAuth 2.1 store: { e } " )
2025-05-13 12:36:53 -04:00
2025-06-07 16:16:48 -04:00
return create_success_response ( verified_user_id )
2025-05-11 10:07:37 -04:00
except Exception as e :
2025-08-05 14:34:11 -04:00
logger . error ( f "Error processing OAuth callback: { str ( e ) } " , exc_info = True )
2025-06-07 16:16:48 -04:00
return create_server_error_response ( str ( e ))
2025-05-20 14:51:10 -04:00
2025-12-13 13:49:28 -08:00
2025-05-20 14:51:10 -04:00
@server.tool ()
2025-12-13 13:49:28 -08:00
async def start_google_auth (
service_name : str , user_google_email : str = USER_GOOGLE_EMAIL
) -> str :
2025-08-10 17:40:27 -04:00
"""
Manually initiate Google OAuth authentication flow.
2025-08-12 08:45:24 -04:00
NOTE: This tool should typically NOT be called directly. The authentication system
2025-08-10 17:40:27 -04:00
automatically handles credential checks and prompts for authentication when needed.
Only use this tool if:
1. You need to re-authenticate with different credentials
2. You want to proactively authenticate before using other tools
3. The automatic authentication flow failed and you need to retry
2025-08-12 08:45:24 -04:00
In most cases, simply try calling the Google Workspace tool you need - it will
2025-08-10 17:40:27 -04:00
automatically handle authentication if required.
"""
2025-08-05 14:34:11 -04:00
if not user_google_email :
raise ValueError ( "user_google_email must be provided." )
2025-08-03 12:15:44 -04:00
2025-08-05 14:34:11 -04:00
error_message = check_client_secrets ()
if error_message :
return f "**Authentication Error:** { error_message } "
2025-08-02 15:40:23 -04:00
2025-08-05 14:34:11 -04:00
try :
2025-08-07 15:58:22 -04:00
auth_message = await start_auth_flow (
user_google_email = user_google_email ,
service_name = service_name ,
2025-12-13 13:49:28 -08:00
redirect_uri = get_oauth_redirect_uri_for_current_mode (),
2025-08-05 14:34:11 -04:00
)
2025-08-07 15:58:22 -04:00
return auth_message
2025-08-05 14:34:11 -04:00
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 } "