55"""
66
77import base64
8+ import logging
89import os
910import sys
1011from typing import List , Dict , Any , Optional , Union
2526from googleapiclient .discovery import build
2627import google .auth
2728
29+ logger = logging .getLogger (__name__ )
30+
2831# Constants
2932SCOPES = ['https://www.googleapis.com/auth/spreadsheets' , 'https://www.googleapis.com/auth/drive' ]
3033CREDENTIALS_CONFIG = os .environ .get ('CREDENTIALS_CONFIG' )
3336SERVICE_ACCOUNT_PATH = os .environ .get ('SERVICE_ACCOUNT_PATH' , 'service_account.json' )
3437DRIVE_FOLDER_ID = os .environ .get ('DRIVE_FOLDER_ID' , '' ) # Working directory in Google Drive
3538
39+
40+ def _configure_logging () -> None :
41+ """Configure CLI logging without overriding host application logging."""
42+ level_name = os .environ .get ("LOG_LEVEL" ) or ("DEBUG" if os .environ .get ("DEBUG" ) else "INFO" )
43+ level = getattr (logging , level_name .upper (), logging .INFO )
44+ if not logging .getLogger ().handlers :
45+ logging .basicConfig (level = level , stream = sys .stderr , format = "%(message)s" )
46+ logger .setLevel (level )
47+
3648# Tool filtering configuration
3749# Parse enabled tools from environment variable or command-line argument
3850def _parse_enabled_tools () -> Optional [set ]:
@@ -86,15 +98,15 @@ async def spreadsheet_lifespan(server: FastMCP) -> AsyncIterator[SpreadsheetCont
8698 SERVICE_ACCOUNT_PATH ,
8799 scopes = SCOPES
88100 )
89- print ("Using service account authentication" )
90- print ( f "Working with Google Drive folder ID: { DRIVE_FOLDER_ID or ' Not specified' } " )
101+ logger . info ("Using service account authentication" )
102+ logger . info ( "Working with Google Drive folder ID: %s" , DRIVE_FOLDER_ID or " Not specified" )
91103 except Exception as e :
92- print ( f "Error using service account authentication: { e } " )
104+ logger . error ( "Error using service account authentication: %s" , e )
93105 creds = None
94106
95107 # Fall back to OAuth flow if service account auth failed or not configured
96108 if not creds :
97- print ("Trying OAuth authentication flow" )
109+ logger . info ("Trying OAuth authentication flow" )
98110 if os .path .exists (TOKEN_PATH ):
99111 with open (TOKEN_PATH , 'r' ) as token :
100112 creds = Credentials .from_authorized_user_info (json .load (token ), SCOPES )
@@ -103,15 +115,15 @@ async def spreadsheet_lifespan(server: FastMCP) -> AsyncIterator[SpreadsheetCont
103115 if not creds or not creds .valid :
104116 if creds and creds .expired and creds .refresh_token :
105117 try :
106- print ("Attempting to refresh expired token..." )
118+ logger . info ("Attempting to refresh expired token..." )
107119 creds .refresh (Request ())
108- print ("Token refreshed successfully" )
120+ logger . info ("Token refreshed successfully" )
109121 # Save the refreshed token
110122 with open (TOKEN_PATH , 'w' ) as token :
111123 token .write (creds .to_json ())
112124 except Exception as refresh_error :
113- print ( f "Token refresh failed: { refresh_error } " )
114- print ("Triggering reauthentication flow..." )
125+ logger . error ( "Token refresh failed: %s" , refresh_error )
126+ logger . info ("Triggering reauthentication flow..." )
115127 creds = None # Clear creds to trigger OAuth flow below
116128
117129 # If refresh failed or creds don't exist, run OAuth flow
@@ -123,28 +135,28 @@ async def spreadsheet_lifespan(server: FastMCP) -> AsyncIterator[SpreadsheetCont
123135 # Save the credentials for the next run
124136 with open (TOKEN_PATH , 'w' ) as token :
125137 token .write (creds .to_json ())
126- print ("Successfully authenticated using OAuth flow" )
138+ logger . info ("Successfully authenticated using OAuth flow" )
127139 except Exception as e :
128- print ( f "Error with OAuth flow: { e } " )
140+ logger . error ( "Error with OAuth flow: %s" , e )
129141 creds = None
130142
131143 # Try Application Default Credentials if no creds thus far
132144 # This will automatically check GOOGLE_APPLICATION_CREDENTIALS, gcloud auth, and metadata service
133145 if not creds :
134146 try :
135- print ("Attempting to use Application Default Credentials (ADC)" )
136- print ("ADC will check: GOOGLE_APPLICATION_CREDENTIALS, gcloud auth, and metadata service" )
147+ logger . info ("Attempting to use Application Default Credentials (ADC)" )
148+ logger . info ("ADC will check: GOOGLE_APPLICATION_CREDENTIALS, gcloud auth, and metadata service" )
137149 creds , project = google .auth .default (
138150 scopes = SCOPES
139151 )
140- print ( f "Successfully authenticated using ADC for project: { project } " )
152+ logger . info ( "Successfully authenticated using ADC for project: %s" , project )
141153 except Exception as e :
142- print ( f "Error using Application Default Credentials: { e } " )
154+ logger . error ( "Error using Application Default Credentials: %s" , e )
143155 raise Exception ("All authentication methods failed. Please configure credentials." )
144156
145157 # Build the services
146- sheets_service = build ('sheets' , 'v4' , credentials = creds )
147- drive_service = build ('drive' , 'v3' , credentials = creds )
158+ sheets_service = build ('sheets' , 'v4' , credentials = creds , cache_discovery = False )
159+ drive_service = build ('drive' , 'v3' , credentials = creds , cache_discovery = False )
148160
149161 try :
150162 # Provide the service in the context
@@ -907,7 +919,7 @@ def create_spreadsheet(title: str, folder_id: Optional[str] = None, ctx: Context
907919 spreadsheet_id = spreadsheet .get ('id' )
908920 parents = spreadsheet .get ('parents' )
909921 folder_info = f" in folder { target_folder_id } " if target_folder_id else " in root"
910- print ( f "Spreadsheet created with ID: { spreadsheet_id } { folder_info } " )
922+ logger . info ( "Spreadsheet created with ID: %s%s" , spreadsheet_id , folder_info )
911923
912924 return {
913925 'spreadsheetId' : spreadsheet_id ,
@@ -994,9 +1006,9 @@ def list_spreadsheets(folder_id: Optional[str] = None, ctx: Context = None) -> L
9941006 # If a specific folder is provided or configured, search only in that folder
9951007 if target_folder_id :
9961008 query += f" and '{ target_folder_id } ' in parents"
997- print ( f "Searching for spreadsheets in folder: { target_folder_id } " )
1009+ logger . info ( "Searching for spreadsheets in folder: %s" , target_folder_id )
9981010 else :
999- print ("Searching for spreadsheets in 'My Drive'" )
1011+ logger . info ("Searching for spreadsheets in 'My Drive'" )
10001012
10011013 # List spreadsheets
10021014 results = drive_service .files ().list (
@@ -1122,11 +1134,11 @@ def list_folders(parent_folder_id: Optional[str] = None, ctx: Context = None) ->
11221134 # If a specific parent folder is provided, search only within that folder
11231135 if parent_folder_id :
11241136 query += f" and '{ parent_folder_id } ' in parents"
1125- print ( f "Searching for folders in parent folder: { parent_folder_id } " )
1137+ logger . info ( "Searching for folders in parent folder: %s" , parent_folder_id )
11261138 else :
11271139 # Search in root of My Drive (folders that don't have any parent folders)
11281140 query += " and 'root' in parents"
1129- print ("Searching for folders in 'My Drive' root" )
1141+ logger . info ("Searching for folders in 'My Drive' root" )
11301142
11311143 # List folders
11321144 results = drive_service .files ().list (
@@ -1716,11 +1728,13 @@ def add_chart(spreadsheet_id: str,
17161728
17171729
17181730def main ():
1731+ _configure_logging ()
1732+
17191733 # Log tool filtering configuration if enabled
17201734 if ENABLED_TOOLS is not None :
1721- print ( f "Tool filtering enabled. Active tools: { ', ' .join (sorted (ENABLED_TOOLS ))} " )
1735+ logger . info ( "Tool filtering enabled. Active tools: %s" , ', ' .join (sorted (ENABLED_TOOLS )))
17221736 else :
1723- print ("Tool filtering disabled. All tools are enabled." )
1737+ logger . info ("Tool filtering disabled. All tools are enabled." )
17241738
17251739 # Run the server
17261740 transport = "stdio"
0 commit comments