google-tools-mcp 1.0.0 → 1.0.2
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 -0
- package/README.md +233 -0
- package/dist/auth.js +2 -0
- package/dist/clients.js +16 -0
- package/dist/index.js +3 -8
- package/dist/tools/calendar/getBusy.js +64 -0
- package/dist/tools/calendar/getEvents.js +140 -0
- package/dist/tools/calendar/getFree.js +225 -0
- package/dist/tools/calendar/index.js +19 -0
- package/dist/tools/calendar/listCalendars.js +38 -0
- package/dist/tools/calendar/listRecurringInstances.js +81 -0
- package/dist/tools/calendar/manageCalendar.js +121 -0
- package/dist/tools/calendar/manageEvent.js +250 -0
- package/dist/tools/calendar/moveEvent.js +60 -0
- package/dist/tools/drafts.js +165 -0
- package/dist/tools/gmail/drafts.js +165 -165
- package/dist/tools/gmail/labels.js +103 -103
- package/dist/tools/gmail/messages.js +448 -448
- package/dist/tools/gmail/settings.js +528 -528
- package/dist/tools/gmail/threads.js +145 -145
- package/dist/tools/index.js +15 -80
- package/dist/tools/labels.js +103 -0
- package/dist/tools/messages.js +448 -0
- package/dist/tools/settings.js +528 -0
- package/dist/tools/threads.js +145 -0
- package/package.json +1 -1
|
@@ -0,0 +1,250 @@
|
|
|
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: 'manage_event',
|
|
8
|
+
description:
|
|
9
|
+
'Create, update, or delete a calendar event. Supports attendees, Google Meet, reminders, attachments, visibility, and transparency settings.',
|
|
10
|
+
parameters: z.object({
|
|
11
|
+
action: z.enum(['create', 'update', 'delete']).describe('The operation to perform.'),
|
|
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 list_calendars 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
|
+
}),
|
|
73
|
+
execute: async (args, { log }) => {
|
|
74
|
+
const calendar = await getCalendarClient();
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
if (args.action === 'delete') {
|
|
78
|
+
if (!args.event_id) throw new UserError('event_id is required for delete.');
|
|
79
|
+
log.info(`Deleting event ${args.event_id} from ${args.calendar_id}`);
|
|
80
|
+
await calendar.events.delete({
|
|
81
|
+
calendarId: args.calendar_id,
|
|
82
|
+
eventId: args.event_id,
|
|
83
|
+
});
|
|
84
|
+
return JSON.stringify({ success: true, message: `Event ${args.event_id} deleted.` });
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (args.action === 'create') {
|
|
88
|
+
if (!args.summary) throw new UserError('summary is required for create.');
|
|
89
|
+
if (!args.start_time) throw new UserError('start_time is required for create.');
|
|
90
|
+
if (!args.end_time) throw new UserError('end_time is required for create.');
|
|
91
|
+
|
|
92
|
+
const eventBody = buildEventBody(args);
|
|
93
|
+
log.info(`Creating event "${args.summary}" on ${args.calendar_id}`);
|
|
94
|
+
|
|
95
|
+
const params = {
|
|
96
|
+
calendarId: args.calendar_id,
|
|
97
|
+
requestBody: eventBody,
|
|
98
|
+
conferenceDataVersion: args.add_google_meet ? 1 : 0,
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const response = await calendar.events.insert(params);
|
|
102
|
+
return JSON.stringify(formatResult('created', response.data), null, 2);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (args.action === 'update') {
|
|
106
|
+
if (!args.event_id) throw new UserError('event_id is required for update.');
|
|
107
|
+
log.info(`Updating event ${args.event_id} on ${args.calendar_id}`);
|
|
108
|
+
|
|
109
|
+
// Fetch existing event to preserve fields
|
|
110
|
+
const existing = await calendar.events.get({
|
|
111
|
+
calendarId: args.calendar_id,
|
|
112
|
+
eventId: args.event_id,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const eventBody = buildUpdateBody(existing.data, args);
|
|
116
|
+
|
|
117
|
+
const params = {
|
|
118
|
+
calendarId: args.calendar_id,
|
|
119
|
+
eventId: args.event_id,
|
|
120
|
+
requestBody: eventBody,
|
|
121
|
+
conferenceDataVersion: args.add_google_meet !== undefined ? 1 : 0,
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const response = await calendar.events.update(params);
|
|
125
|
+
return JSON.stringify(formatResult('updated', response.data), null, 2);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
throw new UserError(`Unknown action: ${args.action}`);
|
|
129
|
+
} catch (error) {
|
|
130
|
+
if (error instanceof UserError) throw error;
|
|
131
|
+
log.error(`Error in manage_event (${args.action}): ${error.message || error}`);
|
|
132
|
+
if (error.code === 404)
|
|
133
|
+
throw new UserError('Calendar or event not found. Check the IDs.');
|
|
134
|
+
if (error.code === 403)
|
|
135
|
+
throw new UserError('Permission denied. You may not have edit access to this calendar.');
|
|
136
|
+
throw new UserError(`Failed to ${args.action} event: ${error.message || 'Unknown error'}`);
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function buildTimeField(timeStr, timezone) {
|
|
143
|
+
// All-day events use 'date', timed events use 'dateTime'
|
|
144
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(timeStr)) {
|
|
145
|
+
return { date: timeStr };
|
|
146
|
+
}
|
|
147
|
+
const field = { dateTime: timeStr };
|
|
148
|
+
if (timezone) field.timeZone = timezone;
|
|
149
|
+
return field;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function buildEventBody(args) {
|
|
153
|
+
const body = {
|
|
154
|
+
summary: args.summary,
|
|
155
|
+
start: buildTimeField(args.start_time, args.timezone),
|
|
156
|
+
end: buildTimeField(args.end_time, args.timezone),
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
if (args.description) body.description = args.description;
|
|
160
|
+
if (args.location) body.location = args.location;
|
|
161
|
+
if (args.attendees) body.attendees = args.attendees.map((email) => ({ email }));
|
|
162
|
+
if (args.transparency) body.transparency = args.transparency;
|
|
163
|
+
if (args.visibility) body.visibility = args.visibility;
|
|
164
|
+
if (args.color_id) body.colorId = args.color_id;
|
|
165
|
+
if (args.recurrence) body.recurrence = args.recurrence;
|
|
166
|
+
|
|
167
|
+
if (args.reminders) {
|
|
168
|
+
body.reminders = {
|
|
169
|
+
useDefault: false,
|
|
170
|
+
overrides: args.reminders,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (args.add_google_meet) {
|
|
175
|
+
body.conferenceData = {
|
|
176
|
+
createRequest: {
|
|
177
|
+
requestId: `meet-${Date.now()}`,
|
|
178
|
+
conferenceSolutionKey: { type: 'hangoutsMeet' },
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return body;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function buildUpdateBody(existing, args) {
|
|
187
|
+
const body = { ...existing };
|
|
188
|
+
|
|
189
|
+
// Remove read-only fields
|
|
190
|
+
delete body.etag;
|
|
191
|
+
delete body.kind;
|
|
192
|
+
delete body.created;
|
|
193
|
+
delete body.updated;
|
|
194
|
+
delete body.creator;
|
|
195
|
+
delete body.organizer;
|
|
196
|
+
delete body.iCalUID;
|
|
197
|
+
delete body.sequence;
|
|
198
|
+
delete body.hangoutLink;
|
|
199
|
+
|
|
200
|
+
if (args.summary !== undefined) body.summary = args.summary;
|
|
201
|
+
if (args.description !== undefined) body.description = args.description;
|
|
202
|
+
if (args.location !== undefined) body.location = args.location;
|
|
203
|
+
if (args.transparency !== undefined) body.transparency = args.transparency;
|
|
204
|
+
if (args.visibility !== undefined) body.visibility = args.visibility;
|
|
205
|
+
if (args.color_id !== undefined) body.colorId = args.color_id;
|
|
206
|
+
|
|
207
|
+
if (args.start_time) body.start = buildTimeField(args.start_time, args.timezone);
|
|
208
|
+
if (args.end_time) body.end = buildTimeField(args.end_time, args.timezone);
|
|
209
|
+
|
|
210
|
+
if (args.attendees) body.attendees = args.attendees.map((email) => ({ email }));
|
|
211
|
+
|
|
212
|
+
if (args.reminders) {
|
|
213
|
+
body.reminders = {
|
|
214
|
+
useDefault: false,
|
|
215
|
+
overrides: args.reminders,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (args.add_google_meet === true) {
|
|
220
|
+
body.conferenceData = {
|
|
221
|
+
createRequest: {
|
|
222
|
+
requestId: `meet-${Date.now()}`,
|
|
223
|
+
conferenceSolutionKey: { type: 'hangoutsMeet' },
|
|
224
|
+
},
|
|
225
|
+
};
|
|
226
|
+
} else if (args.add_google_meet === false) {
|
|
227
|
+
body.conferenceData = null;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return body;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function formatResult(action, event) {
|
|
234
|
+
const result = {
|
|
235
|
+
success: true,
|
|
236
|
+
action,
|
|
237
|
+
id: event.id,
|
|
238
|
+
summary: event.summary,
|
|
239
|
+
start: event.start?.dateTime || event.start?.date,
|
|
240
|
+
end: event.end?.dateTime || event.end?.date,
|
|
241
|
+
htmlLink: event.htmlLink,
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
if (event.hangoutLink) result.meetLink = event.hangoutLink;
|
|
245
|
+
if (event.attendees?.length) {
|
|
246
|
+
result.attendees = event.attendees.map((a) => a.email);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return result;
|
|
250
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
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: 'move_event',
|
|
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
|
+
}),
|
|
21
|
+
execute: async (args, { log }) => {
|
|
22
|
+
const calendar = await getCalendarClient();
|
|
23
|
+
log.info(
|
|
24
|
+
`Moving event ${args.event_id} from ${args.source_calendar_id} to ${args.destination_calendar_id}`
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const response = await calendar.events.move({
|
|
29
|
+
calendarId: args.source_calendar_id,
|
|
30
|
+
eventId: args.event_id,
|
|
31
|
+
destination: args.destination_calendar_id,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const event = response.data;
|
|
35
|
+
return JSON.stringify(
|
|
36
|
+
{
|
|
37
|
+
success: true,
|
|
38
|
+
id: event.id,
|
|
39
|
+
summary: event.summary,
|
|
40
|
+
movedTo: args.destination_calendar_id,
|
|
41
|
+
htmlLink: event.htmlLink,
|
|
42
|
+
},
|
|
43
|
+
null,
|
|
44
|
+
2
|
|
45
|
+
);
|
|
46
|
+
} catch (error) {
|
|
47
|
+
log.error(`Error moving event: ${error.message || error}`);
|
|
48
|
+
if (error.code === 404)
|
|
49
|
+
throw new UserError(
|
|
50
|
+
'Event or calendar not found. Check the event_id and calendar IDs.'
|
|
51
|
+
);
|
|
52
|
+
if (error.code === 403)
|
|
53
|
+
throw new UserError(
|
|
54
|
+
'Permission denied. You need edit access to both source and destination calendars.'
|
|
55
|
+
);
|
|
56
|
+
throw new UserError(`Failed to move event: ${error.message || 'Unknown error'}`);
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
// Gmail Draft tools
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { UserError } from 'fastmcp';
|
|
4
|
+
import { getGmailClient } from '../clients.js';
|
|
5
|
+
import { processMessagePart, constructRawMessage, constructRawMessageWithAttachments } from '../helpers.js';
|
|
6
|
+
|
|
7
|
+
export function register(server) {
|
|
8
|
+
server.addTool({
|
|
9
|
+
name: 'create_draft',
|
|
10
|
+
description: 'Create a draft email in Gmail. Note the mechanics of the raw parameter.',
|
|
11
|
+
parameters: z.object({
|
|
12
|
+
raw: z.string().optional().describe("The entire email message in base64url encoded RFC 2822 format, ignores params.to, cc, bcc, subject, body, includeBodyHtml if provided"),
|
|
13
|
+
threadId: z.string().optional().describe("The thread ID to associate this draft with"),
|
|
14
|
+
to: z.array(z.string()).optional().describe("List of recipient email addresses"),
|
|
15
|
+
cc: z.array(z.string()).optional().describe("List of CC recipient email addresses"),
|
|
16
|
+
bcc: z.array(z.string()).optional().describe("List of BCC recipient email addresses"),
|
|
17
|
+
subject: z.string().optional().describe("The subject of the email"),
|
|
18
|
+
body: z.string().optional().describe("The body of the email"),
|
|
19
|
+
attachments: z.array(z.object({
|
|
20
|
+
filename: z.string().describe("Attachment file name"),
|
|
21
|
+
mimeType: z.string().describe("MIME type of the attachment"),
|
|
22
|
+
base64Data: z.string().describe("Base64 encoded attachment data"),
|
|
23
|
+
})).optional().describe("File attachments to include"),
|
|
24
|
+
includeBodyHtml: z.boolean().optional().describe("Whether to include the parsed HTML in the return for each body"),
|
|
25
|
+
}),
|
|
26
|
+
execute: async (params, { log }) => {
|
|
27
|
+
const gmail = await getGmailClient();
|
|
28
|
+
let raw = params.raw;
|
|
29
|
+
if (!raw) {
|
|
30
|
+
if (params.attachments?.length) {
|
|
31
|
+
raw = await constructRawMessageWithAttachments(gmail, params);
|
|
32
|
+
} else {
|
|
33
|
+
raw = await constructRawMessage(gmail, params);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const createParams = { userId: 'me', requestBody: { message: { raw } } };
|
|
37
|
+
if (params.threadId && createParams.requestBody?.message) {
|
|
38
|
+
createParams.requestBody.message.threadId = params.threadId;
|
|
39
|
+
}
|
|
40
|
+
const { data } = await gmail.users.drafts.create(createParams);
|
|
41
|
+
if (data.message?.payload) {
|
|
42
|
+
data.message.payload = processMessagePart(data.message.payload, params.includeBodyHtml);
|
|
43
|
+
}
|
|
44
|
+
return JSON.stringify(data);
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
server.addTool({
|
|
49
|
+
name: 'update_draft',
|
|
50
|
+
description: 'Update an existing draft\'s content. Replaces the draft message with new content.',
|
|
51
|
+
parameters: z.object({
|
|
52
|
+
id: z.string().describe("The ID of the draft to update"),
|
|
53
|
+
raw: z.string().optional().describe("The entire email message in base64url encoded RFC 2822 format, ignores other params if provided"),
|
|
54
|
+
threadId: z.string().optional().describe("The thread ID to associate this draft with"),
|
|
55
|
+
to: z.array(z.string()).optional().describe("List of recipient email addresses"),
|
|
56
|
+
cc: z.array(z.string()).optional().describe("List of CC recipient email addresses"),
|
|
57
|
+
bcc: z.array(z.string()).optional().describe("List of BCC recipient email addresses"),
|
|
58
|
+
subject: z.string().optional().describe("The subject of the email"),
|
|
59
|
+
body: z.string().optional().describe("The body of the email"),
|
|
60
|
+
attachments: z.array(z.object({
|
|
61
|
+
filename: z.string().describe("Attachment file name"),
|
|
62
|
+
mimeType: z.string().describe("MIME type of the attachment"),
|
|
63
|
+
base64Data: z.string().describe("Base64 encoded attachment data"),
|
|
64
|
+
})).optional().describe("File attachments to include"),
|
|
65
|
+
includeBodyHtml: z.boolean().optional().describe("Whether to include the parsed HTML in the return for each body"),
|
|
66
|
+
}),
|
|
67
|
+
execute: async (params) => {
|
|
68
|
+
const gmail = await getGmailClient();
|
|
69
|
+
let raw = params.raw;
|
|
70
|
+
if (!raw) {
|
|
71
|
+
if (params.attachments?.length) {
|
|
72
|
+
raw = await constructRawMessageWithAttachments(gmail, params);
|
|
73
|
+
} else {
|
|
74
|
+
raw = await constructRawMessage(gmail, params);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const updateParams = { userId: 'me', id: params.id, requestBody: { message: { raw } } };
|
|
78
|
+
if (params.threadId && updateParams.requestBody?.message) {
|
|
79
|
+
updateParams.requestBody.message.threadId = params.threadId;
|
|
80
|
+
}
|
|
81
|
+
const { data } = await gmail.users.drafts.update(updateParams);
|
|
82
|
+
if (data.message?.payload) {
|
|
83
|
+
data.message.payload = processMessagePart(data.message.payload, params.includeBodyHtml);
|
|
84
|
+
}
|
|
85
|
+
return JSON.stringify(data);
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
server.addTool({
|
|
90
|
+
name: 'delete_draft',
|
|
91
|
+
description: 'Delete a draft',
|
|
92
|
+
parameters: z.object({
|
|
93
|
+
id: z.string().describe("The ID of the draft to delete"),
|
|
94
|
+
}),
|
|
95
|
+
execute: async (params) => {
|
|
96
|
+
const gmail = await getGmailClient();
|
|
97
|
+
const { data } = await gmail.users.drafts.delete({ userId: 'me', id: params.id });
|
|
98
|
+
return JSON.stringify(data || { success: true });
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
server.addTool({
|
|
103
|
+
name: 'get_draft',
|
|
104
|
+
description: 'Get a specific draft by ID',
|
|
105
|
+
parameters: z.object({
|
|
106
|
+
id: z.string().describe("The ID of the draft to retrieve"),
|
|
107
|
+
includeBodyHtml: z.boolean().optional().describe("Whether to include the parsed HTML in the return for each body"),
|
|
108
|
+
}),
|
|
109
|
+
execute: async (params) => {
|
|
110
|
+
const gmail = await getGmailClient();
|
|
111
|
+
const { data } = await gmail.users.drafts.get({ userId: 'me', id: params.id, format: 'full' });
|
|
112
|
+
if (data.message?.payload) {
|
|
113
|
+
data.message.payload = processMessagePart(data.message.payload, params.includeBodyHtml);
|
|
114
|
+
}
|
|
115
|
+
return JSON.stringify(data);
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
server.addTool({
|
|
120
|
+
name: 'list_drafts',
|
|
121
|
+
description: 'List drafts in the user\'s mailbox',
|
|
122
|
+
parameters: z.object({
|
|
123
|
+
maxResults: z.number().optional().describe("Maximum number of drafts to return (1-500)"),
|
|
124
|
+
q: z.string().optional().describe("Only return drafts matching the specified query"),
|
|
125
|
+
includeSpamTrash: z.boolean().optional().describe("Include drafts from SPAM and TRASH"),
|
|
126
|
+
includeBodyHtml: z.boolean().optional().describe("Whether to include the parsed HTML in the return for each body"),
|
|
127
|
+
}),
|
|
128
|
+
execute: async (params) => {
|
|
129
|
+
const gmail = await getGmailClient();
|
|
130
|
+
let drafts = [];
|
|
131
|
+
const { data } = await gmail.users.drafts.list({ userId: 'me', ...params });
|
|
132
|
+
drafts.push(...(data.drafts || []));
|
|
133
|
+
let pageToken = data.nextPageToken;
|
|
134
|
+
while (pageToken) {
|
|
135
|
+
const { data: nextData } = await gmail.users.drafts.list({ userId: 'me', ...params, pageToken });
|
|
136
|
+
drafts.push(...(nextData.drafts || []));
|
|
137
|
+
pageToken = nextData.nextPageToken;
|
|
138
|
+
}
|
|
139
|
+
drafts = drafts.map(draft => {
|
|
140
|
+
if (draft.message?.payload) {
|
|
141
|
+
draft.message.payload = processMessagePart(draft.message.payload, params.includeBodyHtml);
|
|
142
|
+
}
|
|
143
|
+
return draft;
|
|
144
|
+
});
|
|
145
|
+
return JSON.stringify(drafts);
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
server.addTool({
|
|
150
|
+
name: 'send_draft',
|
|
151
|
+
description: 'Send an existing draft',
|
|
152
|
+
parameters: z.object({
|
|
153
|
+
id: z.string().describe("The ID of the draft to send"),
|
|
154
|
+
}),
|
|
155
|
+
execute: async (params) => {
|
|
156
|
+
const gmail = await getGmailClient();
|
|
157
|
+
try {
|
|
158
|
+
const { data } = await gmail.users.drafts.send({ userId: 'me', requestBody: { id: params.id } });
|
|
159
|
+
return JSON.stringify(data);
|
|
160
|
+
} catch (error) {
|
|
161
|
+
throw new UserError('Error sending draft, are you sure you have at least one recipient?');
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
}
|