99import json
1010import logging
1111
12+ import httpx
1213from mcp .types import TextContent , ToolAnnotations
1314
1415from everyrow_mcp .app import mcp
2930logger = logging .getLogger (__name__ )
3031
3132
33+ def _error_message (e : Exception ) -> str :
34+ """Format a user-friendly error message from a Google API exception."""
35+ if isinstance (e , httpx .HTTPStatusError ):
36+ status = e .response .status_code
37+ if status == 403 :
38+ return "Permission denied. Check that the spreadsheet is shared with you."
39+ if status == 404 :
40+ return "Spreadsheet not found. Check the spreadsheet ID or URL."
41+ if status == 429 :
42+ return "Rate limited by Google API. Please try again in a moment."
43+ return f"Google API error (HTTP { status } ): { e .response .text } "
44+ return f"Error: { e !r} "
45+
46+
3247@mcp .tool (
3348 name = "sheets_list" ,
3449 annotations = ToolAnnotations (
4156)
4257async def sheets_list (params : SheetsListInput ) -> list [TextContent ]:
4358 """List the user's Google Sheets, optionally filtered by name."""
44- token = await get_google_token ()
45- async with GoogleSheetsClient (token ) as client :
46- files = await client .list_spreadsheets (
47- query = params .query , max_results = params .max_results
48- )
59+ try :
60+ token = await get_google_token ()
61+ async with GoogleSheetsClient (token ) as client :
62+ files = await client .list_spreadsheets (
63+ query = params .query , max_results = params .max_results
64+ )
65+ except Exception as e :
66+ return [TextContent (type = "text" , text = _error_message (e ))]
4967
5068 if not files :
5169 msg = "No spreadsheets found"
@@ -83,9 +101,14 @@ async def sheets_read(params: SheetsReadInput) -> list[TextContent]:
83101 everyrow_agent(input_json=data, task="Research each company")
84102 sheets_write(spreadsheet_id="...", data=enriched_results)
85103 """
86- token = await get_google_token ()
87- async with GoogleSheetsClient (token ) as client :
88- values = await client .read_range (params .spreadsheet_id , params .range )
104+ try :
105+ token = await get_google_token ()
106+ async with GoogleSheetsClient (token ) as client :
107+ values = await client .read_range (
108+ params .spreadsheet_id , cell_range = params .range
109+ )
110+ except Exception as e :
111+ return [TextContent (type = "text" , text = _error_message (e ))]
89112
90113 records = values_to_records (values )
91114
@@ -127,36 +150,41 @@ async def sheets_write(params: SheetsWriteInput) -> list[TextContent]:
127150
128151 Use append=True to add rows after existing data instead of overwriting.
129152 """
130- token = await get_google_token ()
131- values = records_to_values (params .data )
132-
133- async with GoogleSheetsClient (token ) as client :
134- if params .append :
135- result = await client .append_range (
136- params .spreadsheet_id , params .range , values
137- )
138- updated_range = result .get ("updates" , {}).get ("updatedRange" , params .range )
139- updated_rows = result .get ("updates" , {}).get (
140- "updatedRows" , len (params .data )
141- )
142- return [
143- TextContent (
144- type = "text" ,
145- text = f"Appended { updated_rows } rows to { updated_range } ." ,
153+ try :
154+ token = await get_google_token ()
155+ values = records_to_values (params .data )
156+
157+ async with GoogleSheetsClient (token ) as client :
158+ if params .append :
159+ result = await client .append_range (
160+ params .spreadsheet_id , cell_range = params .range , values = values
146161 )
147- ]
148- else :
149- result = await client .write_range (
150- params .spreadsheet_id , params .range , values
151- )
152- updated_range = result .get ("updatedRange" , params .range )
153- updated_rows = result .get ("updatedRows" , len (params .data ) + 1 )
154- return [
155- TextContent (
156- type = "text" ,
157- text = f"Wrote { updated_rows } rows (including header) to { updated_range } ." ,
162+ updated_range = result .get ("updates" , {}).get (
163+ "updatedRange" , params .range
164+ )
165+ updated_rows = result .get ("updates" , {}).get (
166+ "updatedRows" , len (params .data )
158167 )
159- ]
168+ return [
169+ TextContent (
170+ type = "text" ,
171+ text = f"Appended { updated_rows } rows to { updated_range } ." ,
172+ )
173+ ]
174+ else :
175+ result = await client .write_range (
176+ params .spreadsheet_id , cell_range = params .range , values = values
177+ )
178+ updated_range = result .get ("updatedRange" , params .range )
179+ updated_rows = result .get ("updatedRows" , len (params .data ) + 1 )
180+ return [
181+ TextContent (
182+ type = "text" ,
183+ text = f"Wrote { updated_rows } rows (including header) to { updated_range } ." ,
184+ )
185+ ]
186+ except Exception as e :
187+ return [TextContent (type = "text" , text = _error_message (e ))]
160188
161189
162190@mcp .tool (
@@ -174,20 +202,23 @@ async def sheets_create(params: SheetsCreateInput) -> list[TextContent]:
174202
175203 Returns the spreadsheet ID and URL.
176204 """
177- token = await get_google_token ()
178-
179- async with GoogleSheetsClient (token ) as client :
180- metadata = await client .create_spreadsheet (params .title )
181- spreadsheet_id = metadata ["spreadsheetId" ]
182- url = metadata .get (
183- "spreadsheetUrl" ,
184- f"https://docs.google.com/spreadsheets/d/{ spreadsheet_id } " ,
185- )
205+ try :
206+ token = await get_google_token ()
207+
208+ async with GoogleSheetsClient (token ) as client :
209+ metadata = await client .create_spreadsheet (params .title )
210+ spreadsheet_id = metadata ["spreadsheetId" ]
211+ url = metadata .get (
212+ "spreadsheetUrl" ,
213+ f"https://docs.google.com/spreadsheets/d/{ spreadsheet_id } " ,
214+ )
186215
187- # Optionally populate with initial data
188- if params .data :
189- values = records_to_values (params .data )
190- await client .write_range (spreadsheet_id , "Sheet1" , values )
216+ # Optionally populate with initial data
217+ if params .data :
218+ values = records_to_values (params .data )
219+ await client .write_range (spreadsheet_id , "Sheet1" , values )
220+ except Exception as e :
221+ return [TextContent (type = "text" , text = _error_message (e ))]
191222
192223 result = {
193224 "spreadsheet_id" : spreadsheet_id ,
@@ -217,10 +248,13 @@ async def sheets_create(params: SheetsCreateInput) -> list[TextContent]:
217248)
218249async def sheets_info (params : SheetsInfoInput ) -> list [TextContent ]:
219250 """Get metadata about a Google Sheet: title, sheet names, and dimensions."""
220- token = await get_google_token ()
251+ try :
252+ token = await get_google_token ()
221253
222- async with GoogleSheetsClient (token ) as client :
223- metadata = await client .get_spreadsheet_metadata (params .spreadsheet_id )
254+ async with GoogleSheetsClient (token ) as client :
255+ metadata = await client .get_spreadsheet_metadata (params .spreadsheet_id )
256+ except Exception as e :
257+ return [TextContent (type = "text" , text = _error_message (e ))]
224258
225259 title = metadata .get ("properties" , {}).get ("title" , "Unknown" )
226260 sheets = []
0 commit comments