universal-mcp 0.1.1__py3-none-any.whl → 0.1.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- universal_mcp/applications/__init__.py +23 -28
- universal_mcp/applications/application.py +13 -8
- universal_mcp/applications/e2b/app.py +74 -0
- universal_mcp/applications/firecrawl/app.py +381 -0
- universal_mcp/applications/github/README.md +35 -0
- universal_mcp/applications/github/app.py +133 -100
- universal_mcp/applications/google_calendar/app.py +170 -139
- universal_mcp/applications/google_mail/app.py +185 -160
- universal_mcp/applications/markitdown/app.py +32 -0
- universal_mcp/applications/notion/README.md +32 -0
- universal_mcp/applications/notion/__init__.py +0 -0
- universal_mcp/applications/notion/app.py +415 -0
- universal_mcp/applications/reddit/app.py +112 -71
- universal_mcp/applications/resend/app.py +3 -8
- universal_mcp/applications/serp/app.py +84 -0
- universal_mcp/applications/tavily/app.py +11 -10
- universal_mcp/applications/zenquotes/app.py +3 -3
- universal_mcp/cli.py +98 -16
- universal_mcp/config.py +20 -3
- universal_mcp/exceptions.py +1 -3
- universal_mcp/integrations/__init__.py +6 -2
- universal_mcp/integrations/agentr.py +26 -24
- universal_mcp/integrations/integration.py +72 -35
- universal_mcp/servers/__init__.py +21 -1
- universal_mcp/servers/server.py +77 -44
- universal_mcp/stores/__init__.py +15 -2
- universal_mcp/stores/store.py +123 -13
- universal_mcp/utils/__init__.py +1 -0
- universal_mcp/utils/api_generator.py +269 -0
- universal_mcp/utils/docgen.py +360 -0
- universal_mcp/utils/installation.py +17 -2
- universal_mcp/utils/openapi.py +216 -111
- {universal_mcp-0.1.1.dist-info → universal_mcp-0.1.2.dist-info}/METADATA +23 -5
- universal_mcp-0.1.2.dist-info/RECORD +40 -0
- universal_mcp-0.1.1.dist-info/RECORD +0 -29
- {universal_mcp-0.1.1.dist-info → universal_mcp-0.1.2.dist-info}/WHEEL +0 -0
- {universal_mcp-0.1.1.dist-info → universal_mcp-0.1.2.dist-info}/entry_points.txt +0 -0
@@ -1,7 +1,10 @@
|
|
1
|
+
from datetime import datetime, timedelta
|
2
|
+
|
3
|
+
from loguru import logger
|
4
|
+
|
1
5
|
from universal_mcp.applications.application import APIApplication
|
2
6
|
from universal_mcp.integrations import Integration
|
3
|
-
|
4
|
-
from datetime import datetime, timedelta
|
7
|
+
|
5
8
|
|
6
9
|
class GoogleCalendarApp(APIApplication):
|
7
10
|
def __init__(self, integration: Integration) -> None:
|
@@ -16,22 +19,22 @@ class GoogleCalendarApp(APIApplication):
|
|
16
19
|
return credentials["headers"]
|
17
20
|
return {
|
18
21
|
"Authorization": f"Bearer {credentials['access_token']}",
|
19
|
-
"Accept": "application/json"
|
22
|
+
"Accept": "application/json",
|
20
23
|
}
|
21
|
-
|
24
|
+
|
22
25
|
def _format_datetime(self, dt_string: str) -> str:
|
23
26
|
"""Format a datetime string from ISO format to a human-readable format.
|
24
|
-
|
27
|
+
|
25
28
|
Args:
|
26
29
|
dt_string: A datetime string in ISO format (e.g., "2023-06-01T10:00:00Z")
|
27
|
-
|
30
|
+
|
28
31
|
Returns:
|
29
32
|
A formatted datetime string (e.g., "2023-06-01 10:00 AM") or the original string with
|
30
33
|
"(All day)" appended if it's just a date
|
31
34
|
"""
|
32
35
|
if not dt_string or dt_string == "Unknown":
|
33
36
|
return "Unknown"
|
34
|
-
|
37
|
+
|
35
38
|
# Check if it's just a date (all-day event) or a datetime
|
36
39
|
if "T" in dt_string:
|
37
40
|
# It's a datetime - parse and format it
|
@@ -39,10 +42,10 @@ class GoogleCalendarApp(APIApplication):
|
|
39
42
|
# Handle Z (UTC) suffix by replacing with +00:00 timezone
|
40
43
|
if dt_string.endswith("Z"):
|
41
44
|
dt_string = dt_string.replace("Z", "+00:00")
|
42
|
-
|
45
|
+
|
43
46
|
# Parse the ISO datetime string
|
44
47
|
dt = datetime.fromisoformat(dt_string)
|
45
|
-
|
48
|
+
|
46
49
|
# Format to a more readable form
|
47
50
|
return dt.strftime("%Y-%m-%d %I:%M %p")
|
48
51
|
except ValueError:
|
@@ -52,61 +55,67 @@ class GoogleCalendarApp(APIApplication):
|
|
52
55
|
else:
|
53
56
|
# It's just a date (all-day event)
|
54
57
|
return f"{dt_string} (All day)"
|
55
|
-
|
56
|
-
def get_today_events(
|
58
|
+
|
59
|
+
def get_today_events(
|
60
|
+
self, days: int = 1, max_results: int = None, time_zone: str = None
|
61
|
+
) -> str:
|
57
62
|
"""Get events from your Google Calendar for today or a specified number of days
|
58
|
-
|
63
|
+
|
59
64
|
Args:
|
60
65
|
days: Number of days to retrieve events for (default: 1, which is just today)
|
61
66
|
max_results: Maximum number of events to return (optional)
|
62
67
|
time_zone: Time zone used in the response (optional, default is calendar's time zone)
|
63
|
-
|
68
|
+
|
64
69
|
Returns:
|
65
70
|
A formatted list of events or an error message
|
66
71
|
"""
|
67
72
|
# Get today's date in ISO format
|
68
73
|
today = datetime.utcnow().date()
|
69
74
|
end_date = today + timedelta(days=days)
|
70
|
-
|
75
|
+
|
71
76
|
# Format dates for API
|
72
77
|
time_min = f"{today.isoformat()}T00:00:00Z"
|
73
78
|
time_max = f"{end_date.isoformat()}T00:00:00Z"
|
74
|
-
|
79
|
+
|
75
80
|
url = f"{self.base_api_url}/events"
|
76
|
-
|
81
|
+
|
77
82
|
# Build query parameters
|
78
83
|
params = {
|
79
84
|
"timeMin": time_min,
|
80
85
|
"timeMax": time_max,
|
81
86
|
"singleEvents": "true",
|
82
|
-
"orderBy": "startTime"
|
87
|
+
"orderBy": "startTime",
|
83
88
|
}
|
84
|
-
|
89
|
+
|
85
90
|
if max_results is not None:
|
86
91
|
params["maxResults"] = max_results
|
87
|
-
|
92
|
+
|
88
93
|
if time_zone:
|
89
94
|
params["timeZone"] = time_zone
|
90
|
-
|
95
|
+
|
91
96
|
date_range = "today" if days == 1 else f"the next {days} days"
|
92
97
|
logger.info(f"Retrieving calendar events for {date_range}")
|
93
|
-
|
98
|
+
|
94
99
|
response = self._get(url, params=params)
|
95
100
|
response.raise_for_status()
|
96
|
-
|
101
|
+
|
97
102
|
events = response.json().get("items", [])
|
98
103
|
if not events:
|
99
104
|
return f"No events scheduled for {date_range}."
|
100
|
-
|
105
|
+
|
101
106
|
result = f"Events for {date_range}:\n\n"
|
102
107
|
for event in events:
|
103
108
|
# Extract event date and time
|
104
109
|
start = event.get("start", {})
|
105
|
-
event_date =
|
106
|
-
|
110
|
+
event_date = (
|
111
|
+
start.get("date", start.get("dateTime", "")).split("T")[0]
|
112
|
+
if "T" in start.get("dateTime", "")
|
113
|
+
else start.get("date", "")
|
114
|
+
)
|
115
|
+
|
107
116
|
# Extract and format time
|
108
117
|
start_time = start.get("dateTime", start.get("date", "All day"))
|
109
|
-
|
118
|
+
|
110
119
|
# Format the time display
|
111
120
|
if "T" in start_time: # It's a datetime
|
112
121
|
formatted_time = self._format_datetime(start_time)
|
@@ -115,71 +124,74 @@ class GoogleCalendarApp(APIApplication):
|
|
115
124
|
time_display = formatted_time
|
116
125
|
else:
|
117
126
|
# Extract just the time part
|
118
|
-
time_display =
|
127
|
+
time_display = (
|
128
|
+
formatted_time.split(" ")[1]
|
129
|
+
+ " "
|
130
|
+
+ formatted_time.split(" ")[2]
|
131
|
+
)
|
119
132
|
else: # It's an all-day event
|
120
|
-
if days > 1
|
121
|
-
|
122
|
-
else:
|
123
|
-
time_display = "All day"
|
124
|
-
|
133
|
+
time_display = f"{event_date} (All day)" if days > 1 else "All day"
|
134
|
+
|
125
135
|
# Get event details
|
126
136
|
summary = event.get("summary", "Untitled event")
|
127
137
|
event_id = event.get("id", "No ID")
|
128
|
-
|
138
|
+
|
129
139
|
result += f"- {time_display}: {summary} (ID: {event_id})\n"
|
130
|
-
|
140
|
+
|
131
141
|
return result
|
132
142
|
|
133
|
-
def get_event(
|
143
|
+
def get_event(
|
144
|
+
self, event_id: str, max_attendees: int = None, time_zone: str = None
|
145
|
+
) -> str:
|
134
146
|
"""Get a specific event from your Google Calendar by ID
|
135
|
-
|
147
|
+
|
136
148
|
Args:
|
137
149
|
event_id: The ID of the event to retrieve
|
138
150
|
max_attendees: Optional. The maximum number of attendees to include in the response
|
139
151
|
time_zone: Optional. Time zone used in the response (default is calendar's time zone)
|
140
|
-
|
152
|
+
|
141
153
|
Returns:
|
142
154
|
A formatted event details or an error message
|
143
155
|
"""
|
144
156
|
url = f"{self.base_api_url}/events/{event_id}"
|
145
|
-
|
157
|
+
|
146
158
|
# Build query parameters
|
147
159
|
params = {}
|
148
160
|
if max_attendees is not None:
|
149
161
|
params["maxAttendees"] = max_attendees
|
150
162
|
if time_zone:
|
151
163
|
params["timeZone"] = time_zone
|
152
|
-
|
164
|
+
|
153
165
|
logger.info(f"Retrieving calendar event with ID: {event_id}")
|
154
|
-
|
166
|
+
|
155
167
|
response = self._get(url, params=params)
|
156
168
|
response.raise_for_status()
|
157
|
-
|
169
|
+
|
158
170
|
event = response.json()
|
159
|
-
|
171
|
+
|
160
172
|
# Extract event details
|
161
173
|
summary = event.get("summary", "Untitled event")
|
162
174
|
description = event.get("description", "No description")
|
163
175
|
location = event.get("location", "No location specified")
|
164
|
-
|
176
|
+
|
165
177
|
# Format dates
|
166
178
|
start = event.get("start", {})
|
167
179
|
end = event.get("end", {})
|
168
|
-
|
180
|
+
|
169
181
|
start_time = start.get("dateTime", start.get("date", "Unknown"))
|
170
182
|
end_time = end.get("dateTime", end.get("date", "Unknown"))
|
171
|
-
|
183
|
+
|
172
184
|
# Format datetimes using the helper function
|
173
185
|
start_formatted = self._format_datetime(start_time)
|
174
186
|
end_formatted = self._format_datetime(end_time)
|
175
|
-
|
187
|
+
|
176
188
|
# Get creator and organizer
|
177
189
|
creator = event.get("creator", {}).get("email", "Unknown")
|
178
190
|
organizer = event.get("organizer", {}).get("email", "Unknown")
|
179
|
-
|
191
|
+
|
180
192
|
# Check if it's a recurring event
|
181
193
|
recurrence = "Yes" if "recurrence" in event else "No"
|
182
|
-
|
194
|
+
|
183
195
|
# Get attendees if any
|
184
196
|
attendees = event.get("attendees", [])
|
185
197
|
attendee_info = ""
|
@@ -189,17 +201,17 @@ class GoogleCalendarApp(APIApplication):
|
|
189
201
|
email = attendee.get("email", "No email")
|
190
202
|
name = attendee.get("displayName", email)
|
191
203
|
response_status = attendee.get("responseStatus", "Unknown")
|
192
|
-
|
204
|
+
|
193
205
|
status_mapping = {
|
194
206
|
"accepted": "Accepted",
|
195
207
|
"declined": "Declined",
|
196
208
|
"tentative": "Maybe",
|
197
|
-
"needsAction": "Not responded"
|
209
|
+
"needsAction": "Not responded",
|
198
210
|
}
|
199
|
-
|
211
|
+
|
200
212
|
formatted_status = status_mapping.get(response_status, response_status)
|
201
213
|
attendee_info += f" {i}. {name} ({email}) - {formatted_status}\n"
|
202
|
-
|
214
|
+
|
203
215
|
# Format the response
|
204
216
|
result = f"Event: {summary}\n"
|
205
217
|
result += f"ID: {event_id}\n"
|
@@ -210,14 +222,22 @@ class GoogleCalendarApp(APIApplication):
|
|
210
222
|
result += f"Organizer: {organizer}\n"
|
211
223
|
result += f"Recurring: {recurrence}\n"
|
212
224
|
result += attendee_info
|
213
|
-
|
225
|
+
|
214
226
|
return result
|
215
227
|
|
216
|
-
def list_events(
|
217
|
-
|
218
|
-
|
228
|
+
def list_events(
|
229
|
+
self,
|
230
|
+
max_results: int = 10,
|
231
|
+
time_min: str = None,
|
232
|
+
time_max: str = None,
|
233
|
+
q: str = None,
|
234
|
+
order_by: str = "startTime",
|
235
|
+
single_events: bool = True,
|
236
|
+
time_zone: str = None,
|
237
|
+
page_token: str = None,
|
238
|
+
) -> str:
|
219
239
|
"""List events from your Google Calendar with various filtering options
|
220
|
-
|
240
|
+
|
221
241
|
Args:
|
222
242
|
max_results: Maximum number of events to return (default: 10, max: 2500)
|
223
243
|
time_min: Start time (ISO format, e.g. '2023-12-01T00:00:00Z') - defaults to now if not specified
|
@@ -227,19 +247,19 @@ class GoogleCalendarApp(APIApplication):
|
|
227
247
|
single_events: Whether to expand recurring events (default: True)
|
228
248
|
time_zone: Time zone used in the response (default is calendar's time zone)
|
229
249
|
page_token: Token for retrieving a specific page of results
|
230
|
-
|
250
|
+
|
231
251
|
Returns:
|
232
252
|
A formatted list of events or an error message
|
233
253
|
"""
|
234
254
|
url = f"{self.base_api_url}/events"
|
235
|
-
|
255
|
+
|
236
256
|
# Build query parameters
|
237
257
|
params = {
|
238
258
|
"maxResults": max_results,
|
239
259
|
"singleEvents": str(single_events).lower(),
|
240
|
-
"orderBy": order_by
|
260
|
+
"orderBy": order_by,
|
241
261
|
}
|
242
|
-
|
262
|
+
|
243
263
|
# Set time boundaries if provided, otherwise default to now for time_min
|
244
264
|
if time_min:
|
245
265
|
params["timeMin"] = time_min
|
@@ -247,145 +267,151 @@ class GoogleCalendarApp(APIApplication):
|
|
247
267
|
# Default to current time if not specified
|
248
268
|
now = datetime.utcnow().isoformat() + "Z" # 'Z' indicates UTC time
|
249
269
|
params["timeMin"] = now
|
250
|
-
|
270
|
+
|
251
271
|
if time_max:
|
252
272
|
params["timeMax"] = time_max
|
253
|
-
|
273
|
+
|
254
274
|
# Add optional filters if provided
|
255
275
|
if q:
|
256
276
|
params["q"] = q
|
257
|
-
|
277
|
+
|
258
278
|
if time_zone:
|
259
279
|
params["timeZone"] = time_zone
|
260
|
-
|
280
|
+
|
261
281
|
if page_token:
|
262
282
|
params["pageToken"] = page_token
|
263
|
-
|
283
|
+
|
264
284
|
logger.info(f"Retrieving calendar events with params: {params}")
|
265
|
-
|
285
|
+
|
266
286
|
response = self._get(url, params=params)
|
267
287
|
response.raise_for_status()
|
268
|
-
|
288
|
+
|
269
289
|
data = response.json()
|
270
290
|
events = data.get("items", [])
|
271
|
-
|
291
|
+
|
272
292
|
if not events:
|
273
293
|
return "No events found matching your criteria."
|
274
|
-
|
294
|
+
|
275
295
|
# Extract calendar information
|
276
296
|
calendar_summary = data.get("summary", "Your Calendar")
|
277
297
|
time_zone_info = data.get("timeZone", "Unknown")
|
278
|
-
|
298
|
+
|
279
299
|
result = f"Events from {calendar_summary} (Time Zone: {time_zone_info}):\n\n"
|
280
|
-
|
300
|
+
|
281
301
|
# Process and format each event
|
282
302
|
for i, event in enumerate(events, 1):
|
283
303
|
# Get basic event details
|
284
304
|
event_id = event.get("id", "No ID")
|
285
305
|
summary = event.get("summary", "Untitled event")
|
286
|
-
|
306
|
+
|
287
307
|
# Get event times and format them
|
288
308
|
start = event.get("start", {})
|
289
309
|
start_time = start.get("dateTime", start.get("date", "Unknown"))
|
290
|
-
|
310
|
+
|
291
311
|
# Format the start time using the helper function
|
292
312
|
start_formatted = self._format_datetime(start_time)
|
293
|
-
|
313
|
+
|
294
314
|
# Get location if available
|
295
315
|
location = event.get("location", "No location specified")
|
296
|
-
|
316
|
+
|
297
317
|
# Check if it's a recurring event
|
298
318
|
is_recurring = "recurrence" in event
|
299
319
|
recurring_info = " (Recurring)" if is_recurring else ""
|
300
|
-
|
320
|
+
|
301
321
|
# Format the event information
|
302
322
|
result += f"{i}. {summary}{recurring_info}\n"
|
303
323
|
result += f" ID: {event_id}\n"
|
304
324
|
result += f" When: {start_formatted}\n"
|
305
325
|
result += f" Where: {location}\n"
|
306
|
-
|
326
|
+
|
307
327
|
# Add a separator between events
|
308
328
|
if i < len(events):
|
309
329
|
result += "\n"
|
310
|
-
|
330
|
+
|
311
331
|
# Add pagination info if available
|
312
332
|
if "nextPageToken" in data:
|
313
333
|
next_token = data.get("nextPageToken")
|
314
|
-
result +=
|
315
|
-
|
334
|
+
result += (
|
335
|
+
f"\nMore events available. Use page_token='{next_token}' to see more."
|
336
|
+
)
|
337
|
+
|
316
338
|
return result
|
317
339
|
|
318
340
|
def quick_add_event(self, text: str, send_updates: str = "none") -> str:
|
319
341
|
"""Create a calendar event using natural language description
|
320
|
-
|
342
|
+
|
321
343
|
This method allows you to quickly create an event using a simple text string,
|
322
344
|
similar to how you would add events in the Google Calendar UI.
|
323
|
-
|
345
|
+
|
324
346
|
Args:
|
325
347
|
text: Text describing the event (e.g., "Meeting with John at Coffee Shop tomorrow 3pm-4pm")
|
326
348
|
send_updates: Who should receive notifications - "all", "externalOnly", or "none" (default)
|
327
|
-
|
349
|
+
|
328
350
|
Returns:
|
329
351
|
A confirmation message with the created event details or an error message
|
330
352
|
"""
|
331
353
|
url = f"{self.base_api_url}/events/quickAdd"
|
332
|
-
|
354
|
+
|
333
355
|
# Use params argument instead of manually constructing URL
|
334
|
-
params = {
|
335
|
-
|
336
|
-
"sendUpdates": send_updates
|
337
|
-
}
|
338
|
-
|
356
|
+
params = {"text": text, "sendUpdates": send_updates}
|
357
|
+
|
339
358
|
logger.info(f"Creating event via quickAdd: '{text}'")
|
340
|
-
|
359
|
+
|
341
360
|
# Pass params to _post method
|
342
361
|
response = self._post(url, data=None, params=params)
|
343
362
|
response.raise_for_status()
|
344
|
-
|
363
|
+
|
345
364
|
event = response.json()
|
346
|
-
|
365
|
+
|
347
366
|
# Extract event details
|
348
367
|
event_id = event.get("id", "Unknown")
|
349
368
|
summary = event.get("summary", "Untitled event")
|
350
|
-
|
369
|
+
|
351
370
|
# Format dates
|
352
371
|
start = event.get("start", {})
|
353
372
|
end = event.get("end", {})
|
354
|
-
|
373
|
+
|
355
374
|
start_time = start.get("dateTime", start.get("date", "Unknown"))
|
356
375
|
end_time = end.get("dateTime", end.get("date", "Unknown"))
|
357
|
-
|
376
|
+
|
358
377
|
# Format datetimes using the helper function
|
359
378
|
start_formatted = self._format_datetime(start_time)
|
360
379
|
end_formatted = self._format_datetime(end_time)
|
361
|
-
|
380
|
+
|
362
381
|
# Get location if available
|
363
382
|
location = event.get("location", "No location specified")
|
364
|
-
|
383
|
+
|
365
384
|
# Format the confirmation message
|
366
|
-
result =
|
385
|
+
result = "Successfully created event!\n\n"
|
367
386
|
result += f"Summary: {summary}\n"
|
368
387
|
result += f"When: {start_formatted}"
|
369
|
-
|
388
|
+
|
370
389
|
# Only add end time if it's different from start (for all-day events they might be the same)
|
371
390
|
if start_formatted != end_formatted:
|
372
391
|
result += f" to {end_formatted}"
|
373
|
-
|
392
|
+
|
374
393
|
result += f"\nWhere: {location}\n"
|
375
394
|
result += f"Event ID: {event_id}\n"
|
376
|
-
|
395
|
+
|
377
396
|
# Add a note about viewing the event
|
378
397
|
result += f"\nUse get_event('{event_id}') to see full details."
|
379
|
-
|
398
|
+
|
380
399
|
return result
|
381
|
-
|
382
|
-
def get_event_instances(
|
383
|
-
|
384
|
-
|
400
|
+
|
401
|
+
def get_event_instances(
|
402
|
+
self,
|
403
|
+
event_id: str,
|
404
|
+
max_results: int = 25,
|
405
|
+
time_min: str = None,
|
406
|
+
time_max: str = None,
|
407
|
+
time_zone: str = None,
|
408
|
+
show_deleted: bool = False,
|
409
|
+
page_token: str = None,
|
410
|
+
) -> str:
|
385
411
|
"""Get all instances of a recurring event
|
386
|
-
|
412
|
+
|
387
413
|
This method retrieves all occurrences of a recurring event within a specified time range.
|
388
|
-
|
414
|
+
|
389
415
|
Args:
|
390
416
|
event_id: ID of the recurring event
|
391
417
|
max_results: Maximum number of event instances to return (default: 25, max: 2500)
|
@@ -394,94 +420,99 @@ class GoogleCalendarApp(APIApplication):
|
|
394
420
|
time_zone: Time zone used in the response (default is calendar's time zone)
|
395
421
|
show_deleted: Whether to include deleted instances (default: False)
|
396
422
|
page_token: Token for retrieving a specific page of results
|
397
|
-
|
423
|
+
|
398
424
|
Returns:
|
399
425
|
A formatted list of event instances or an error message
|
400
426
|
"""
|
401
427
|
url = f"{self.base_api_url}/events/{event_id}/instances"
|
402
|
-
|
428
|
+
|
403
429
|
# Build query parameters
|
404
|
-
params = {
|
405
|
-
|
406
|
-
"showDeleted": str(show_deleted).lower()
|
407
|
-
}
|
408
|
-
|
430
|
+
params = {"maxResults": max_results, "showDeleted": str(show_deleted).lower()}
|
431
|
+
|
409
432
|
# Add optional parameters if provided
|
410
433
|
if time_min:
|
411
434
|
params["timeMin"] = time_min
|
412
|
-
|
435
|
+
|
413
436
|
if time_max:
|
414
437
|
params["timeMax"] = time_max
|
415
|
-
|
438
|
+
|
416
439
|
if time_zone:
|
417
440
|
params["timeZone"] = time_zone
|
418
|
-
|
441
|
+
|
419
442
|
if page_token:
|
420
443
|
params["pageToken"] = page_token
|
421
|
-
|
444
|
+
|
422
445
|
logger.info(f"Retrieving instances of recurring event with ID: {event_id}")
|
423
|
-
|
446
|
+
|
424
447
|
response = self._get(url, params=params)
|
425
448
|
response.raise_for_status()
|
426
|
-
|
449
|
+
|
427
450
|
data = response.json()
|
428
451
|
instances = data.get("items", [])
|
429
|
-
|
452
|
+
|
430
453
|
if not instances:
|
431
454
|
return f"No instances found for recurring event with ID: {event_id}"
|
432
|
-
|
455
|
+
|
433
456
|
# Extract event summary from the first instance
|
434
457
|
parent_summary = instances[0].get("summary", "Untitled recurring event")
|
435
|
-
|
458
|
+
|
436
459
|
result = f"Instances of recurring event: {parent_summary}\n\n"
|
437
|
-
|
460
|
+
|
438
461
|
# Process and format each instance
|
439
462
|
for i, instance in enumerate(instances, 1):
|
440
463
|
# Get instance ID and status
|
441
464
|
instance_id = instance.get("id", "No ID")
|
442
465
|
status = instance.get("status", "confirmed")
|
443
|
-
|
466
|
+
|
444
467
|
# Format status for display
|
445
468
|
status_display = ""
|
446
469
|
if status == "cancelled":
|
447
470
|
status_display = " [CANCELLED]"
|
448
471
|
elif status == "tentative":
|
449
472
|
status_display = " [TENTATIVE]"
|
450
|
-
|
473
|
+
|
451
474
|
# Get instance time
|
452
475
|
start = instance.get("start", {})
|
453
476
|
original_start_time = instance.get("originalStartTime", {})
|
454
|
-
|
477
|
+
|
455
478
|
# Determine if this is a modified instance
|
456
479
|
is_modified = original_start_time and "dateTime" in original_start_time
|
457
480
|
modified_indicator = " [MODIFIED]" if is_modified else ""
|
458
|
-
|
481
|
+
|
459
482
|
# Get the time information
|
460
483
|
start_time = start.get("dateTime", start.get("date", "Unknown"))
|
461
|
-
|
484
|
+
|
462
485
|
# Format the time using the helper function
|
463
486
|
formatted_time = self._format_datetime(start_time)
|
464
|
-
|
487
|
+
|
465
488
|
# Format the instance information
|
466
489
|
result += f"{i}. {formatted_time}{status_display}{modified_indicator}\n"
|
467
490
|
result += f" Instance ID: {instance_id}\n"
|
468
|
-
|
491
|
+
|
469
492
|
# Show original start time if modified
|
470
493
|
if is_modified:
|
471
|
-
orig_time = original_start_time.get(
|
494
|
+
orig_time = original_start_time.get(
|
495
|
+
"dateTime", original_start_time.get("date", "Unknown")
|
496
|
+
)
|
472
497
|
orig_formatted = self._format_datetime(orig_time)
|
473
498
|
result += f" Original time: {orig_formatted}\n"
|
474
|
-
|
499
|
+
|
475
500
|
# Add a separator between instances
|
476
501
|
if i < len(instances):
|
477
502
|
result += "\n"
|
478
|
-
|
503
|
+
|
479
504
|
# Add pagination info if available
|
480
505
|
if "nextPageToken" in data:
|
481
506
|
next_token = data.get("nextPageToken")
|
482
507
|
result += f"\nMore instances available. Use page_token='{next_token}' to see more."
|
483
|
-
|
508
|
+
|
484
509
|
return result
|
485
|
-
|
510
|
+
|
486
511
|
def list_tools(self):
|
487
|
-
return [
|
512
|
+
return [
|
513
|
+
self.get_event,
|
514
|
+
self.get_today_events,
|
515
|
+
self.list_events,
|
516
|
+
self.quick_add_event,
|
517
|
+
self.get_event_instances,
|
518
|
+
]
|