google-tools-mcp 1.2.11 → 2.0.0
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.
- package/LICENSE +21 -21
- package/README.md +555 -276
- package/dist/cachedToolsList.js +52 -52
- package/dist/googleDocsApiHelpers.js +76 -12
- package/dist/helpers.js +572 -252
- package/dist/httpAuth.js +296 -0
- package/dist/index.js +162 -11
- package/dist/logger.js +87 -87
- package/dist/markdown-transformer/docsToMarkdown.js +222 -21
- package/dist/markdown-transformer/index.js +19 -9
- package/dist/markdown-transformer/markdownToDocs.js +458 -28
- package/dist/readTracker.js +136 -17
- package/dist/sessionContext.js +37 -0
- package/dist/setup.js +187 -9
- package/dist/tools/calendar/getBusy.js +64 -64
- package/dist/tools/calendar/getEvents.js +141 -141
- package/dist/tools/calendar/getFree.js +225 -225
- package/dist/tools/calendar/index.js +19 -19
- package/dist/tools/calendar/listCalendars.js +38 -38
- package/dist/tools/calendar/listRecurringInstances.js +83 -83
- package/dist/tools/calendar/manageCalendar.js +121 -121
- package/dist/tools/calendar/manageEvent.js +258 -258
- package/dist/tools/calendar/moveEvent.js +66 -66
- package/dist/tools/docs/addTab.js +10 -12
- package/dist/tools/docs/appendToGoogleDoc.js +9 -4
- package/dist/tools/docs/deleteRange.js +9 -4
- package/dist/tools/docs/findAndReplace.js +9 -4
- package/dist/tools/docs/formatting/applyParagraphStyle.js +4 -1
- package/dist/tools/docs/formatting/getFormatting.js +171 -171
- package/dist/tools/docs/insertImage.js +15 -2
- package/dist/tools/docs/insertPageBreak.js +4 -1
- package/dist/tools/docs/insertTable.js +4 -1
- package/dist/tools/docs/insertTableWithData.js +4 -1
- package/dist/tools/docs/modifyText.js +14 -4
- package/dist/tools/docs/modifyText.test.js +84 -84
- package/dist/tools/docs/readGoogleDoc.js +71 -11
- package/dist/tools/docs/renameTab.js +12 -14
- package/dist/tools/drafts.js +165 -165
- package/dist/tools/drive/createDocument.js +13 -1
- package/dist/tools/drive/downloadFile.js +268 -268
- package/dist/tools/drive/getFileInfo.js +48 -48
- package/dist/tools/drive/getFilePath.js +50 -50
- package/dist/tools/drive/listDriveFiles.js +112 -112
- package/dist/tools/drive/listSharedDrives.js +52 -52
- package/dist/tools/drive/listSharedWithMe.js +97 -97
- package/dist/tools/drive/uploadFile.js +111 -111
- package/dist/tools/extras/index.js +9 -9
- package/dist/tools/extras/readDriveFile.js +212 -206
- package/dist/tools/extras/readFile.js +84 -84
- package/dist/tools/extras/searchFileContents.js +81 -81
- package/dist/tools/forms/batchUpdateForm.js +81 -81
- package/dist/tools/forms/createForm.js +54 -54
- package/dist/tools/forms/getForm.js +118 -118
- package/dist/tools/forms/getFormResponse.js +45 -45
- package/dist/tools/forms/index.js +15 -15
- package/dist/tools/forms/listFormResponses.js +56 -56
- package/dist/tools/forms/setPublishSettings.js +59 -59
- package/dist/tools/gmail/drafts.js +165 -165
- package/dist/tools/gmail/labels.js +54 -83
- package/dist/tools/gmail/messages.js +444 -441
- package/dist/tools/gmail/settings.js +358 -528
- package/dist/tools/gmail/threads.js +286 -164
- package/dist/tools/index.js +511 -473
- package/dist/tools/labels.js +103 -103
- package/dist/tools/legacyAliases.js +426 -0
- package/dist/tools/maps/directions.js +72 -0
- package/dist/tools/maps/geocode.js +16 -0
- package/dist/tools/maps/index.js +15 -0
- package/dist/tools/maps/mapsClient.js +83 -0
- package/dist/tools/maps/placeDetails.js +16 -0
- package/dist/tools/maps/reverseGeocode.js +19 -0
- package/dist/tools/maps/searchNearby.js +54 -0
- package/dist/tools/maps/searchPlaces.js +24 -0
- package/dist/tools/messages.js +448 -448
- package/dist/tools/settings.js +528 -528
- package/dist/tools/slides/createPresentation.js +104 -104
- package/dist/tools/slides/createShape.js +92 -92
- package/dist/tools/slides/createTextBox.js +82 -82
- package/dist/tools/slides/deleteSlide.js +30 -30
- package/dist/tools/slides/duplicateSlide.js +37 -37
- package/dist/tools/slides/exportThumbnail.js +42 -42
- package/dist/tools/slides/formatParagraph.js +72 -72
- package/dist/tools/slides/formatText.js +84 -84
- package/dist/tools/slides/getPresentation.js +87 -87
- package/dist/tools/slides/index.js +33 -33
- package/dist/tools/slides/reorderSlides.js +41 -41
- package/dist/tools/slides/replaceAllText.js +46 -46
- package/dist/tools/slides/setBackground.js +58 -58
- package/dist/tools/slides/speakerNotes.js +102 -102
- package/dist/tools/slides/styleShape.js +111 -111
- package/dist/tools/slides/updatePresentation.js +128 -128
- package/dist/tools/threads.js +145 -145
- package/dist/tools/utils/appendMarkdownToGoogleDoc.js +31 -7
- package/dist/tools/utils/replaceDocumentWithMarkdown.js +72 -9
- package/dist/updateCheck.js +202 -0
- package/dist/workspace.js +122 -0
- package/package.json +82 -80
|
@@ -1,258 +1,258 @@
|
|
|
1
|
-
import { UserError } from 'fastmcp';
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
import { getCalendarClient } from '../../clients.js';
|
|
4
|
-
|
|
5
|
-
export function register(server) {
|
|
6
|
-
server.addTool({
|
|
7
|
-
name: '
|
|
8
|
-
description:
|
|
9
|
-
'Create, update, or delete a calendar event. Supports attendees, Google Meet, reminders, attachments, visibility, and transparency settings. IMPORTANT: To modify an existing event, always use action "update" with the existing event_id. Never delete and recreate an event to make changes — this destroys the event ID, attendee RSVPs, and sent notifications.',
|
|
10
|
-
parameters: z.object({
|
|
11
|
-
action: z.enum(['create', 'update', 'delete']).describe('The operation to perform. Use "update" to modify existing events — do not delete and recreate. "delete" should only be used to permanently remove an event, not to redo one.'),
|
|
12
|
-
calendar_id: z
|
|
13
|
-
.string()
|
|
14
|
-
.optional()
|
|
15
|
-
.default('primary')
|
|
16
|
-
.describe('Calendar ID. Defaults to "primary".'),
|
|
17
|
-
event_id: z
|
|
18
|
-
.string()
|
|
19
|
-
.optional()
|
|
20
|
-
.describe('Event ID — required for update and delete.'),
|
|
21
|
-
summary: z
|
|
22
|
-
.string()
|
|
23
|
-
.optional()
|
|
24
|
-
.describe('Event title. Required for create.'),
|
|
25
|
-
start_time: z
|
|
26
|
-
.string()
|
|
27
|
-
.optional()
|
|
28
|
-
.describe('Start time in RFC3339 format (e.g. "2025-03-15T10:00:00-05:00") or date for all-day (e.g. "2025-03-15"). Required for create.'),
|
|
29
|
-
end_time: z
|
|
30
|
-
.string()
|
|
31
|
-
.optional()
|
|
32
|
-
.describe('End time in RFC3339 format or date for all-day. Required for create.'),
|
|
33
|
-
description: z.string().optional().describe('Event description/body.'),
|
|
34
|
-
location: z.string().optional().describe('Event location.'),
|
|
35
|
-
attendees: z
|
|
36
|
-
.array(z.string())
|
|
37
|
-
.optional()
|
|
38
|
-
.describe('List of attendee email addresses.'),
|
|
39
|
-
timezone: z
|
|
40
|
-
.string()
|
|
41
|
-
.optional()
|
|
42
|
-
.describe('Timezone for the event (e.g. "America/New_York"). Defaults to calendar timezone.'),
|
|
43
|
-
add_google_meet: z
|
|
44
|
-
.boolean()
|
|
45
|
-
.optional()
|
|
46
|
-
.describe('If true, adds a Google Meet conference link. If false on update, removes it.'),
|
|
47
|
-
reminders: z
|
|
48
|
-
.array(
|
|
49
|
-
z.object({
|
|
50
|
-
method: z.enum(['email', 'popup']).describe('Reminder method.'),
|
|
51
|
-
minutes: z.number().describe('Minutes before the event to send reminder.'),
|
|
52
|
-
})
|
|
53
|
-
)
|
|
54
|
-
.optional()
|
|
55
|
-
.describe('Custom reminders. Overrides calendar defaults.'),
|
|
56
|
-
transparency: z
|
|
57
|
-
.enum(['opaque', 'transparent'])
|
|
58
|
-
.optional()
|
|
59
|
-
.describe('"opaque" = busy (default), "transparent" = free/available.'),
|
|
60
|
-
visibility: z
|
|
61
|
-
.enum(['default', 'public', 'private', 'confidential'])
|
|
62
|
-
.optional()
|
|
63
|
-
.describe('Event visibility.'),
|
|
64
|
-
color_id: z
|
|
65
|
-
.string()
|
|
66
|
-
.optional()
|
|
67
|
-
.describe('Color ID for the event (1-11). Use
|
|
68
|
-
recurrence: z
|
|
69
|
-
.array(z.string())
|
|
70
|
-
.optional()
|
|
71
|
-
.describe('RRULE recurrence rules (e.g. ["RRULE:FREQ=WEEKLY;BYDAY=MO,WE,FR"]). Only for create.'),
|
|
72
|
-
send_updates: z
|
|
73
|
-
.enum(['all', 'externalOnly', 'none'])
|
|
74
|
-
.optional()
|
|
75
|
-
.default('all')
|
|
76
|
-
.describe('Who to send email notifications to: "all" = all attendees (default), "externalOnly" = non-Google Calendar attendees only, "none" = no emails.'),
|
|
77
|
-
}),
|
|
78
|
-
execute: async (args, { log }) => {
|
|
79
|
-
const calendar = await getCalendarClient();
|
|
80
|
-
|
|
81
|
-
try {
|
|
82
|
-
if (args.action === 'delete') {
|
|
83
|
-
if (!args.event_id) throw new UserError('event_id is required for delete.');
|
|
84
|
-
log.info(`Deleting event ${args.event_id} from ${args.calendar_id}`);
|
|
85
|
-
await calendar.events.delete({
|
|
86
|
-
calendarId: args.calendar_id,
|
|
87
|
-
eventId: args.event_id,
|
|
88
|
-
sendUpdates: args.send_updates,
|
|
89
|
-
});
|
|
90
|
-
return JSON.stringify({ success: true, message: `Event ${args.event_id} deleted.` });
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
if (args.action === 'create') {
|
|
94
|
-
if (!args.summary) throw new UserError('summary is required for create.');
|
|
95
|
-
if (!args.start_time) throw new UserError('start_time is required for create.');
|
|
96
|
-
if (!args.end_time) throw new UserError('end_time is required for create.');
|
|
97
|
-
|
|
98
|
-
const eventBody = buildEventBody(args);
|
|
99
|
-
log.info(`Creating event "${args.summary}" on ${args.calendar_id}`);
|
|
100
|
-
|
|
101
|
-
const params = {
|
|
102
|
-
calendarId: args.calendar_id,
|
|
103
|
-
requestBody: eventBody,
|
|
104
|
-
conferenceDataVersion: args.add_google_meet ? 1 : 0,
|
|
105
|
-
sendUpdates: args.send_updates,
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
const response = await calendar.events.insert(params);
|
|
109
|
-
return JSON.stringify(formatResult('created', response.data), null, 2);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
if (args.action === 'update') {
|
|
113
|
-
if (!args.event_id) throw new UserError('event_id is required for update.');
|
|
114
|
-
log.info(`Updating event ${args.event_id} on ${args.calendar_id}`);
|
|
115
|
-
|
|
116
|
-
// Fetch existing event to preserve fields
|
|
117
|
-
const existing = await calendar.events.get({
|
|
118
|
-
calendarId: args.calendar_id,
|
|
119
|
-
eventId: args.event_id,
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
const eventBody = buildUpdateBody(existing.data, args);
|
|
123
|
-
|
|
124
|
-
const params = {
|
|
125
|
-
calendarId: args.calendar_id,
|
|
126
|
-
eventId: args.event_id,
|
|
127
|
-
requestBody: eventBody,
|
|
128
|
-
conferenceDataVersion: args.add_google_meet !== undefined ? 1 : 0,
|
|
129
|
-
sendUpdates: args.send_updates,
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
const response = await calendar.events.update(params);
|
|
133
|
-
return JSON.stringify(formatResult('updated', response.data), null, 2);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
throw new UserError(`Unknown action: ${args.action}`);
|
|
137
|
-
} catch (error) {
|
|
138
|
-
if (error instanceof UserError) throw error;
|
|
139
|
-
log.error(`Error in
|
|
140
|
-
if (error.code === 404)
|
|
141
|
-
throw new UserError('Calendar or event not found. Check the IDs.');
|
|
142
|
-
if (error.code === 403)
|
|
143
|
-
throw new UserError('Permission denied. You may not have edit access to this calendar.');
|
|
144
|
-
throw new UserError(`Failed to ${args.action} event: ${error.message || 'Unknown error'}`);
|
|
145
|
-
}
|
|
146
|
-
},
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
function buildTimeField(timeStr, timezone) {
|
|
151
|
-
// All-day events use 'date', timed events use 'dateTime'
|
|
152
|
-
if (/^\d{4}-\d{2}-\d{2}$/.test(timeStr)) {
|
|
153
|
-
return { date: timeStr };
|
|
154
|
-
}
|
|
155
|
-
const field = { dateTime: timeStr };
|
|
156
|
-
if (timezone) field.timeZone = timezone;
|
|
157
|
-
return field;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
function buildEventBody(args) {
|
|
161
|
-
const body = {
|
|
162
|
-
summary: args.summary,
|
|
163
|
-
start: buildTimeField(args.start_time, args.timezone),
|
|
164
|
-
end: buildTimeField(args.end_time, args.timezone),
|
|
165
|
-
};
|
|
166
|
-
|
|
167
|
-
if (args.description) body.description = args.description;
|
|
168
|
-
if (args.location) body.location = args.location;
|
|
169
|
-
if (args.attendees) body.attendees = args.attendees.map((email) => ({ email }));
|
|
170
|
-
if (args.transparency) body.transparency = args.transparency;
|
|
171
|
-
if (args.visibility) body.visibility = args.visibility;
|
|
172
|
-
if (args.color_id) body.colorId = args.color_id;
|
|
173
|
-
if (args.recurrence) body.recurrence = args.recurrence;
|
|
174
|
-
|
|
175
|
-
if (args.reminders) {
|
|
176
|
-
body.reminders = {
|
|
177
|
-
useDefault: false,
|
|
178
|
-
overrides: args.reminders,
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
if (args.add_google_meet) {
|
|
183
|
-
body.conferenceData = {
|
|
184
|
-
createRequest: {
|
|
185
|
-
requestId: `meet-${Date.now()}`,
|
|
186
|
-
conferenceSolutionKey: { type: 'hangoutsMeet' },
|
|
187
|
-
},
|
|
188
|
-
};
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
return body;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
function buildUpdateBody(existing, args) {
|
|
195
|
-
const body = { ...existing };
|
|
196
|
-
|
|
197
|
-
// Remove read-only fields
|
|
198
|
-
delete body.etag;
|
|
199
|
-
delete body.kind;
|
|
200
|
-
delete body.created;
|
|
201
|
-
delete body.updated;
|
|
202
|
-
delete body.creator;
|
|
203
|
-
delete body.organizer;
|
|
204
|
-
delete body.iCalUID;
|
|
205
|
-
delete body.sequence;
|
|
206
|
-
delete body.hangoutLink;
|
|
207
|
-
|
|
208
|
-
if (args.summary !== undefined) body.summary = args.summary;
|
|
209
|
-
if (args.description !== undefined) body.description = args.description;
|
|
210
|
-
if (args.location !== undefined) body.location = args.location;
|
|
211
|
-
if (args.transparency !== undefined) body.transparency = args.transparency;
|
|
212
|
-
if (args.visibility !== undefined) body.visibility = args.visibility;
|
|
213
|
-
if (args.color_id !== undefined) body.colorId = args.color_id;
|
|
214
|
-
|
|
215
|
-
if (args.start_time) body.start = buildTimeField(args.start_time, args.timezone);
|
|
216
|
-
if (args.end_time) body.end = buildTimeField(args.end_time, args.timezone);
|
|
217
|
-
|
|
218
|
-
if (args.attendees) body.attendees = args.attendees.map((email) => ({ email }));
|
|
219
|
-
|
|
220
|
-
if (args.reminders) {
|
|
221
|
-
body.reminders = {
|
|
222
|
-
useDefault: false,
|
|
223
|
-
overrides: args.reminders,
|
|
224
|
-
};
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
if (args.add_google_meet === true) {
|
|
228
|
-
body.conferenceData = {
|
|
229
|
-
createRequest: {
|
|
230
|
-
requestId: `meet-${Date.now()}`,
|
|
231
|
-
conferenceSolutionKey: { type: 'hangoutsMeet' },
|
|
232
|
-
},
|
|
233
|
-
};
|
|
234
|
-
} else if (args.add_google_meet === false) {
|
|
235
|
-
body.conferenceData = null;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
return body;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
function formatResult(action, event) {
|
|
242
|
-
const result = {
|
|
243
|
-
success: true,
|
|
244
|
-
action,
|
|
245
|
-
id: event.id,
|
|
246
|
-
summary: event.summary,
|
|
247
|
-
start: event.start?.dateTime || event.start?.date,
|
|
248
|
-
end: event.end?.dateTime || event.end?.date,
|
|
249
|
-
htmlLink: event.htmlLink,
|
|
250
|
-
};
|
|
251
|
-
|
|
252
|
-
if (event.hangoutLink) result.meetLink = event.hangoutLink;
|
|
253
|
-
if (event.attendees?.length) {
|
|
254
|
-
result.attendees = event.attendees.map((a) => a.email);
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
return result;
|
|
258
|
-
}
|
|
1
|
+
import { UserError } from 'fastmcp';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getCalendarClient } from '../../clients.js';
|
|
4
|
+
|
|
5
|
+
export function register(server) {
|
|
6
|
+
server.addTool({
|
|
7
|
+
name: 'manageEvent',
|
|
8
|
+
description:
|
|
9
|
+
'Create, update, or delete a calendar event. Supports attendees, Google Meet, reminders, attachments, visibility, and transparency settings. IMPORTANT: To modify an existing event, always use action "update" with the existing event_id. Never delete and recreate an event to make changes — this destroys the event ID, attendee RSVPs, and sent notifications.',
|
|
10
|
+
parameters: z.object({
|
|
11
|
+
action: z.enum(['create', 'update', 'delete']).describe('The operation to perform. Use "update" to modify existing events — do not delete and recreate. "delete" should only be used to permanently remove an event, not to redo one.'),
|
|
12
|
+
calendar_id: z
|
|
13
|
+
.string()
|
|
14
|
+
.optional()
|
|
15
|
+
.default('primary')
|
|
16
|
+
.describe('Calendar ID. Defaults to "primary".'),
|
|
17
|
+
event_id: z
|
|
18
|
+
.string()
|
|
19
|
+
.optional()
|
|
20
|
+
.describe('Event ID — required for update and delete.'),
|
|
21
|
+
summary: z
|
|
22
|
+
.string()
|
|
23
|
+
.optional()
|
|
24
|
+
.describe('Event title. Required for create.'),
|
|
25
|
+
start_time: z
|
|
26
|
+
.string()
|
|
27
|
+
.optional()
|
|
28
|
+
.describe('Start time in RFC3339 format (e.g. "2025-03-15T10:00:00-05:00") or date for all-day (e.g. "2025-03-15"). Required for create.'),
|
|
29
|
+
end_time: z
|
|
30
|
+
.string()
|
|
31
|
+
.optional()
|
|
32
|
+
.describe('End time in RFC3339 format or date for all-day. Required for create.'),
|
|
33
|
+
description: z.string().optional().describe('Event description/body.'),
|
|
34
|
+
location: z.string().optional().describe('Event location.'),
|
|
35
|
+
attendees: z
|
|
36
|
+
.array(z.string())
|
|
37
|
+
.optional()
|
|
38
|
+
.describe('List of attendee email addresses.'),
|
|
39
|
+
timezone: z
|
|
40
|
+
.string()
|
|
41
|
+
.optional()
|
|
42
|
+
.describe('Timezone for the event (e.g. "America/New_York"). Defaults to calendar timezone.'),
|
|
43
|
+
add_google_meet: z
|
|
44
|
+
.boolean()
|
|
45
|
+
.optional()
|
|
46
|
+
.describe('If true, adds a Google Meet conference link. If false on update, removes it.'),
|
|
47
|
+
reminders: z
|
|
48
|
+
.array(
|
|
49
|
+
z.object({
|
|
50
|
+
method: z.enum(['email', 'popup']).describe('Reminder method.'),
|
|
51
|
+
minutes: z.number().describe('Minutes before the event to send reminder.'),
|
|
52
|
+
})
|
|
53
|
+
)
|
|
54
|
+
.optional()
|
|
55
|
+
.describe('Custom reminders. Overrides calendar defaults.'),
|
|
56
|
+
transparency: z
|
|
57
|
+
.enum(['opaque', 'transparent'])
|
|
58
|
+
.optional()
|
|
59
|
+
.describe('"opaque" = busy (default), "transparent" = free/available.'),
|
|
60
|
+
visibility: z
|
|
61
|
+
.enum(['default', 'public', 'private', 'confidential'])
|
|
62
|
+
.optional()
|
|
63
|
+
.describe('Event visibility.'),
|
|
64
|
+
color_id: z
|
|
65
|
+
.string()
|
|
66
|
+
.optional()
|
|
67
|
+
.describe('Color ID for the event (1-11). Use listCalendars or Google Calendar docs for color mapping.'),
|
|
68
|
+
recurrence: z
|
|
69
|
+
.array(z.string())
|
|
70
|
+
.optional()
|
|
71
|
+
.describe('RRULE recurrence rules (e.g. ["RRULE:FREQ=WEEKLY;BYDAY=MO,WE,FR"]). Only for create.'),
|
|
72
|
+
send_updates: z
|
|
73
|
+
.enum(['all', 'externalOnly', 'none'])
|
|
74
|
+
.optional()
|
|
75
|
+
.default('all')
|
|
76
|
+
.describe('Who to send email notifications to: "all" = all attendees (default), "externalOnly" = non-Google Calendar attendees only, "none" = no emails.'),
|
|
77
|
+
}),
|
|
78
|
+
execute: async (args, { log }) => {
|
|
79
|
+
const calendar = await getCalendarClient();
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
if (args.action === 'delete') {
|
|
83
|
+
if (!args.event_id) throw new UserError('event_id is required for delete.');
|
|
84
|
+
log.info(`Deleting event ${args.event_id} from ${args.calendar_id}`);
|
|
85
|
+
await calendar.events.delete({
|
|
86
|
+
calendarId: args.calendar_id,
|
|
87
|
+
eventId: args.event_id,
|
|
88
|
+
sendUpdates: args.send_updates,
|
|
89
|
+
});
|
|
90
|
+
return JSON.stringify({ success: true, message: `Event ${args.event_id} deleted.` });
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (args.action === 'create') {
|
|
94
|
+
if (!args.summary) throw new UserError('summary is required for create.');
|
|
95
|
+
if (!args.start_time) throw new UserError('start_time is required for create.');
|
|
96
|
+
if (!args.end_time) throw new UserError('end_time is required for create.');
|
|
97
|
+
|
|
98
|
+
const eventBody = buildEventBody(args);
|
|
99
|
+
log.info(`Creating event "${args.summary}" on ${args.calendar_id}`);
|
|
100
|
+
|
|
101
|
+
const params = {
|
|
102
|
+
calendarId: args.calendar_id,
|
|
103
|
+
requestBody: eventBody,
|
|
104
|
+
conferenceDataVersion: args.add_google_meet ? 1 : 0,
|
|
105
|
+
sendUpdates: args.send_updates,
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const response = await calendar.events.insert(params);
|
|
109
|
+
return JSON.stringify(formatResult('created', response.data), null, 2);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (args.action === 'update') {
|
|
113
|
+
if (!args.event_id) throw new UserError('event_id is required for update.');
|
|
114
|
+
log.info(`Updating event ${args.event_id} on ${args.calendar_id}`);
|
|
115
|
+
|
|
116
|
+
// Fetch existing event to preserve fields
|
|
117
|
+
const existing = await calendar.events.get({
|
|
118
|
+
calendarId: args.calendar_id,
|
|
119
|
+
eventId: args.event_id,
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const eventBody = buildUpdateBody(existing.data, args);
|
|
123
|
+
|
|
124
|
+
const params = {
|
|
125
|
+
calendarId: args.calendar_id,
|
|
126
|
+
eventId: args.event_id,
|
|
127
|
+
requestBody: eventBody,
|
|
128
|
+
conferenceDataVersion: args.add_google_meet !== undefined ? 1 : 0,
|
|
129
|
+
sendUpdates: args.send_updates,
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const response = await calendar.events.update(params);
|
|
133
|
+
return JSON.stringify(formatResult('updated', response.data), null, 2);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
throw new UserError(`Unknown action: ${args.action}`);
|
|
137
|
+
} catch (error) {
|
|
138
|
+
if (error instanceof UserError) throw error;
|
|
139
|
+
log.error(`Error in manageEvent (${args.action}): ${error.message || error}`);
|
|
140
|
+
if (error.code === 404)
|
|
141
|
+
throw new UserError('Calendar or event not found. Check the IDs.');
|
|
142
|
+
if (error.code === 403)
|
|
143
|
+
throw new UserError('Permission denied. You may not have edit access to this calendar.');
|
|
144
|
+
throw new UserError(`Failed to ${args.action} event: ${error.message || 'Unknown error'}`);
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function buildTimeField(timeStr, timezone) {
|
|
151
|
+
// All-day events use 'date', timed events use 'dateTime'
|
|
152
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(timeStr)) {
|
|
153
|
+
return { date: timeStr };
|
|
154
|
+
}
|
|
155
|
+
const field = { dateTime: timeStr };
|
|
156
|
+
if (timezone) field.timeZone = timezone;
|
|
157
|
+
return field;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function buildEventBody(args) {
|
|
161
|
+
const body = {
|
|
162
|
+
summary: args.summary,
|
|
163
|
+
start: buildTimeField(args.start_time, args.timezone),
|
|
164
|
+
end: buildTimeField(args.end_time, args.timezone),
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
if (args.description) body.description = args.description;
|
|
168
|
+
if (args.location) body.location = args.location;
|
|
169
|
+
if (args.attendees) body.attendees = args.attendees.map((email) => ({ email }));
|
|
170
|
+
if (args.transparency) body.transparency = args.transparency;
|
|
171
|
+
if (args.visibility) body.visibility = args.visibility;
|
|
172
|
+
if (args.color_id) body.colorId = args.color_id;
|
|
173
|
+
if (args.recurrence) body.recurrence = args.recurrence;
|
|
174
|
+
|
|
175
|
+
if (args.reminders) {
|
|
176
|
+
body.reminders = {
|
|
177
|
+
useDefault: false,
|
|
178
|
+
overrides: args.reminders,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (args.add_google_meet) {
|
|
183
|
+
body.conferenceData = {
|
|
184
|
+
createRequest: {
|
|
185
|
+
requestId: `meet-${Date.now()}`,
|
|
186
|
+
conferenceSolutionKey: { type: 'hangoutsMeet' },
|
|
187
|
+
},
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return body;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function buildUpdateBody(existing, args) {
|
|
195
|
+
const body = { ...existing };
|
|
196
|
+
|
|
197
|
+
// Remove read-only fields
|
|
198
|
+
delete body.etag;
|
|
199
|
+
delete body.kind;
|
|
200
|
+
delete body.created;
|
|
201
|
+
delete body.updated;
|
|
202
|
+
delete body.creator;
|
|
203
|
+
delete body.organizer;
|
|
204
|
+
delete body.iCalUID;
|
|
205
|
+
delete body.sequence;
|
|
206
|
+
delete body.hangoutLink;
|
|
207
|
+
|
|
208
|
+
if (args.summary !== undefined) body.summary = args.summary;
|
|
209
|
+
if (args.description !== undefined) body.description = args.description;
|
|
210
|
+
if (args.location !== undefined) body.location = args.location;
|
|
211
|
+
if (args.transparency !== undefined) body.transparency = args.transparency;
|
|
212
|
+
if (args.visibility !== undefined) body.visibility = args.visibility;
|
|
213
|
+
if (args.color_id !== undefined) body.colorId = args.color_id;
|
|
214
|
+
|
|
215
|
+
if (args.start_time) body.start = buildTimeField(args.start_time, args.timezone);
|
|
216
|
+
if (args.end_time) body.end = buildTimeField(args.end_time, args.timezone);
|
|
217
|
+
|
|
218
|
+
if (args.attendees) body.attendees = args.attendees.map((email) => ({ email }));
|
|
219
|
+
|
|
220
|
+
if (args.reminders) {
|
|
221
|
+
body.reminders = {
|
|
222
|
+
useDefault: false,
|
|
223
|
+
overrides: args.reminders,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (args.add_google_meet === true) {
|
|
228
|
+
body.conferenceData = {
|
|
229
|
+
createRequest: {
|
|
230
|
+
requestId: `meet-${Date.now()}`,
|
|
231
|
+
conferenceSolutionKey: { type: 'hangoutsMeet' },
|
|
232
|
+
},
|
|
233
|
+
};
|
|
234
|
+
} else if (args.add_google_meet === false) {
|
|
235
|
+
body.conferenceData = null;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return body;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function formatResult(action, event) {
|
|
242
|
+
const result = {
|
|
243
|
+
success: true,
|
|
244
|
+
action,
|
|
245
|
+
id: event.id,
|
|
246
|
+
summary: event.summary,
|
|
247
|
+
start: event.start?.dateTime || event.start?.date,
|
|
248
|
+
end: event.end?.dateTime || event.end?.date,
|
|
249
|
+
htmlLink: event.htmlLink,
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
if (event.hangoutLink) result.meetLink = event.hangoutLink;
|
|
253
|
+
if (event.attendees?.length) {
|
|
254
|
+
result.attendees = event.attendees.map((a) => a.email);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return result;
|
|
258
|
+
}
|
|
@@ -1,66 +1,66 @@
|
|
|
1
|
-
import { UserError } from 'fastmcp';
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
import { getCalendarClient } from '../../clients.js';
|
|
4
|
-
|
|
5
|
-
export function register(server) {
|
|
6
|
-
server.addTool({
|
|
7
|
-
name: '
|
|
8
|
-
description:
|
|
9
|
-
'Moves an event from one calendar to another. The event is removed from the source calendar and added to the destination.',
|
|
10
|
-
parameters: z.object({
|
|
11
|
-
event_id: z.string().describe('The event ID to move.'),
|
|
12
|
-
source_calendar_id: z
|
|
13
|
-
.string()
|
|
14
|
-
.optional()
|
|
15
|
-
.default('primary')
|
|
16
|
-
.describe('Calendar ID the event is currently in. Defaults to "primary".'),
|
|
17
|
-
destination_calendar_id: z
|
|
18
|
-
.string()
|
|
19
|
-
.describe('Calendar ID to move the event to.'),
|
|
20
|
-
send_updates: z
|
|
21
|
-
.enum(['all', 'externalOnly', 'none'])
|
|
22
|
-
.optional()
|
|
23
|
-
.default('all')
|
|
24
|
-
.describe('Who to send email notifications to: "all" = all attendees (default), "externalOnly" = non-Google Calendar attendees only, "none" = no emails.'),
|
|
25
|
-
}),
|
|
26
|
-
execute: async (args, { log }) => {
|
|
27
|
-
const calendar = await getCalendarClient();
|
|
28
|
-
log.info(
|
|
29
|
-
`Moving event ${args.event_id} from ${args.source_calendar_id} to ${args.destination_calendar_id}`
|
|
30
|
-
);
|
|
31
|
-
|
|
32
|
-
try {
|
|
33
|
-
const response = await calendar.events.move({
|
|
34
|
-
calendarId: args.source_calendar_id,
|
|
35
|
-
eventId: args.event_id,
|
|
36
|
-
destination: args.destination_calendar_id,
|
|
37
|
-
sendUpdates: args.send_updates,
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
const event = response.data;
|
|
41
|
-
return JSON.stringify(
|
|
42
|
-
{
|
|
43
|
-
success: true,
|
|
44
|
-
id: event.id,
|
|
45
|
-
summary: event.summary,
|
|
46
|
-
movedTo: args.destination_calendar_id,
|
|
47
|
-
htmlLink: event.htmlLink,
|
|
48
|
-
},
|
|
49
|
-
null,
|
|
50
|
-
2
|
|
51
|
-
);
|
|
52
|
-
} catch (error) {
|
|
53
|
-
log.error(`Error moving event: ${error.message || error}`);
|
|
54
|
-
if (error.code === 404)
|
|
55
|
-
throw new UserError(
|
|
56
|
-
'Event or calendar not found. Check the event_id and calendar IDs.'
|
|
57
|
-
);
|
|
58
|
-
if (error.code === 403)
|
|
59
|
-
throw new UserError(
|
|
60
|
-
'Permission denied. You need edit access to both source and destination calendars.'
|
|
61
|
-
);
|
|
62
|
-
throw new UserError(`Failed to move event: ${error.message || 'Unknown error'}`);
|
|
63
|
-
}
|
|
64
|
-
},
|
|
65
|
-
});
|
|
66
|
-
}
|
|
1
|
+
import { UserError } from 'fastmcp';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getCalendarClient } from '../../clients.js';
|
|
4
|
+
|
|
5
|
+
export function register(server) {
|
|
6
|
+
server.addTool({
|
|
7
|
+
name: 'moveEvent',
|
|
8
|
+
description:
|
|
9
|
+
'Moves an event from one calendar to another. The event is removed from the source calendar and added to the destination.',
|
|
10
|
+
parameters: z.object({
|
|
11
|
+
event_id: z.string().describe('The event ID to move.'),
|
|
12
|
+
source_calendar_id: z
|
|
13
|
+
.string()
|
|
14
|
+
.optional()
|
|
15
|
+
.default('primary')
|
|
16
|
+
.describe('Calendar ID the event is currently in. Defaults to "primary".'),
|
|
17
|
+
destination_calendar_id: z
|
|
18
|
+
.string()
|
|
19
|
+
.describe('Calendar ID to move the event to.'),
|
|
20
|
+
send_updates: z
|
|
21
|
+
.enum(['all', 'externalOnly', 'none'])
|
|
22
|
+
.optional()
|
|
23
|
+
.default('all')
|
|
24
|
+
.describe('Who to send email notifications to: "all" = all attendees (default), "externalOnly" = non-Google Calendar attendees only, "none" = no emails.'),
|
|
25
|
+
}),
|
|
26
|
+
execute: async (args, { log }) => {
|
|
27
|
+
const calendar = await getCalendarClient();
|
|
28
|
+
log.info(
|
|
29
|
+
`Moving event ${args.event_id} from ${args.source_calendar_id} to ${args.destination_calendar_id}`
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
const response = await calendar.events.move({
|
|
34
|
+
calendarId: args.source_calendar_id,
|
|
35
|
+
eventId: args.event_id,
|
|
36
|
+
destination: args.destination_calendar_id,
|
|
37
|
+
sendUpdates: args.send_updates,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const event = response.data;
|
|
41
|
+
return JSON.stringify(
|
|
42
|
+
{
|
|
43
|
+
success: true,
|
|
44
|
+
id: event.id,
|
|
45
|
+
summary: event.summary,
|
|
46
|
+
movedTo: args.destination_calendar_id,
|
|
47
|
+
htmlLink: event.htmlLink,
|
|
48
|
+
},
|
|
49
|
+
null,
|
|
50
|
+
2
|
|
51
|
+
);
|
|
52
|
+
} catch (error) {
|
|
53
|
+
log.error(`Error moving event: ${error.message || error}`);
|
|
54
|
+
if (error.code === 404)
|
|
55
|
+
throw new UserError(
|
|
56
|
+
'Event or calendar not found. Check the event_id and calendar IDs.'
|
|
57
|
+
);
|
|
58
|
+
if (error.code === 403)
|
|
59
|
+
throw new UserError(
|
|
60
|
+
'Permission denied. You need edit access to both source and destination calendars.'
|
|
61
|
+
);
|
|
62
|
+
throw new UserError(`Failed to move event: ${error.message || 'Unknown error'}`);
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
}
|