google-tools-mcp 1.0.0 → 1.0.1
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 +237 -0
- package/dist/auth.js +2 -0
- package/dist/clients.js +16 -0
- 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 +8 -0
- 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
|
@@ -1,448 +1,448 @@
|
|
|
1
|
-
// Gmail Message tools
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
import { getGmailClient } from '../../clients.js';
|
|
4
|
-
import { processMessagePart, constructRawMessage, constructRawMessageWithAttachments, findHeader, formatEmailList, getNestedHistory, getPlainTextBody, wrapTextBody } from '../../helpers.js';
|
|
5
|
-
|
|
6
|
-
export function register(server) {
|
|
7
|
-
server.addTool({
|
|
8
|
-
name: 'send_message',
|
|
9
|
-
description: 'Send an email message to specified recipients. Note the mechanics of the raw parameter.',
|
|
10
|
-
parameters: z.object({
|
|
11
|
-
raw: z.string().optional().describe("The entire email message in base64url encoded RFC 2822 format, ignores to, cc, bcc, subject, body if provided"),
|
|
12
|
-
threadId: z.string().optional().describe("The thread ID to associate this message with"),
|
|
13
|
-
to: z.array(z.string()).optional().describe("List of recipient email addresses"),
|
|
14
|
-
cc: z.array(z.string()).optional().describe("List of CC recipient email addresses"),
|
|
15
|
-
bcc: z.array(z.string()).optional().describe("List of BCC recipient email addresses"),
|
|
16
|
-
subject: z.string().optional().describe("The subject of the email"),
|
|
17
|
-
body: z.string().optional().describe("The body of the email"),
|
|
18
|
-
attachments: z.array(z.object({
|
|
19
|
-
filename: z.string().describe("Attachment file name"),
|
|
20
|
-
mimeType: z.string().describe("MIME type of the attachment"),
|
|
21
|
-
base64Data: z.string().describe("Base64 encoded attachment data"),
|
|
22
|
-
})).optional().describe("File attachments to include"),
|
|
23
|
-
includeBodyHtml: z.boolean().optional().describe("Whether to include the parsed HTML in the return for each body"),
|
|
24
|
-
}),
|
|
25
|
-
execute: async (params) => {
|
|
26
|
-
const gmail = await getGmailClient();
|
|
27
|
-
let raw = params.raw;
|
|
28
|
-
if (!raw) {
|
|
29
|
-
if (params.attachments?.length) {
|
|
30
|
-
raw = await constructRawMessageWithAttachments(gmail, params);
|
|
31
|
-
} else {
|
|
32
|
-
raw = await constructRawMessage(gmail, params);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
const sendParams = { userId: 'me', requestBody: { raw } };
|
|
36
|
-
if (params.threadId && sendParams.requestBody) {
|
|
37
|
-
sendParams.requestBody.threadId = params.threadId;
|
|
38
|
-
}
|
|
39
|
-
const { data } = await gmail.users.messages.send(sendParams);
|
|
40
|
-
if (data.payload) {
|
|
41
|
-
data.payload = processMessagePart(data.payload, params.includeBodyHtml);
|
|
42
|
-
}
|
|
43
|
-
return JSON.stringify(data);
|
|
44
|
-
},
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
server.addTool({
|
|
48
|
-
name: 'reply_message',
|
|
49
|
-
description: 'Reply to a message. Automatically handles To/Cc recipients, subject prefix, threading headers, and quoted content. Use replyAll to include all original recipients.',
|
|
50
|
-
parameters: z.object({
|
|
51
|
-
messageId: z.string().describe("The ID of the message to reply to"),
|
|
52
|
-
body: z.string().describe("The reply body text"),
|
|
53
|
-
replyAll: z.boolean().optional().describe("If true, reply to all original recipients (To + Cc minus yourself). Default: false"),
|
|
54
|
-
to: z.array(z.string()).optional().describe("Override recipient list (if omitted, replies to sender or Reply-To)"),
|
|
55
|
-
cc: z.array(z.string()).optional().describe("Override CC list (if omitted and replyAll, uses original To + Cc minus yourself)"),
|
|
56
|
-
bcc: z.array(z.string()).optional().describe("Optional BCC recipients"),
|
|
57
|
-
attachments: z.array(z.object({
|
|
58
|
-
filename: z.string().describe("Attachment file name"),
|
|
59
|
-
mimeType: z.string().describe("MIME type of the attachment"),
|
|
60
|
-
base64Data: z.string().describe("Base64 encoded attachment data"),
|
|
61
|
-
})).optional().describe("Optional attachments to include in the reply"),
|
|
62
|
-
includeBodyHtml: z.boolean().optional().describe("Whether to include the parsed HTML in the return"),
|
|
63
|
-
}),
|
|
64
|
-
execute: async (params) => {
|
|
65
|
-
const gmail = await getGmailClient();
|
|
66
|
-
// Fetch original message
|
|
67
|
-
const { data: original } = await gmail.users.messages.get({ userId: 'me', id: params.messageId, format: 'full' });
|
|
68
|
-
const headers = original.payload?.headers || [];
|
|
69
|
-
const threadId = original.threadId;
|
|
70
|
-
// Get sender info for quoted content
|
|
71
|
-
const fromHeader = findHeader(headers, 'reply-to') || findHeader(headers, 'from');
|
|
72
|
-
const dateHeader = findHeader(headers, 'date');
|
|
73
|
-
const originalSubject = findHeader(headers, 'subject') || '';
|
|
74
|
-
const messageIdHeader = findHeader(headers, 'message-id');
|
|
75
|
-
const referencesHeader = findHeader(headers, 'references');
|
|
76
|
-
// Build subject
|
|
77
|
-
let subject = originalSubject;
|
|
78
|
-
if (!subject.toLowerCase().startsWith('re:')) {
|
|
79
|
-
subject = `Re: ${subject}`;
|
|
80
|
-
}
|
|
81
|
-
// Build recipients
|
|
82
|
-
let to = params.to;
|
|
83
|
-
let cc = params.cc;
|
|
84
|
-
if (!to) {
|
|
85
|
-
to = fromHeader ? [fromHeader] : [];
|
|
86
|
-
}
|
|
87
|
-
if (!cc && params.replyAll) {
|
|
88
|
-
// Get own email for exclusion
|
|
89
|
-
const { data: profile } = await gmail.users.getProfile({ userId: 'me' });
|
|
90
|
-
const myEmail = profile.emailAddress?.toLowerCase();
|
|
91
|
-
const originalTo = formatEmailList(findHeader(headers, 'to'));
|
|
92
|
-
const originalCc = formatEmailList(findHeader(headers, 'cc'));
|
|
93
|
-
const allRecipients = [...originalTo, ...originalCc].filter(
|
|
94
|
-
email => !email.toLowerCase().includes(myEmail)
|
|
95
|
-
);
|
|
96
|
-
// Remove the sender from CC since they're in To
|
|
97
|
-
cc = allRecipients.filter(email => !to.some(t => email.toLowerCase().includes(t.toLowerCase())));
|
|
98
|
-
}
|
|
99
|
-
// Build threading headers
|
|
100
|
-
const references = [];
|
|
101
|
-
if (referencesHeader) references.push(...referencesHeader.split(' '));
|
|
102
|
-
if (messageIdHeader) references.push(messageIdHeader);
|
|
103
|
-
// Build quoted content from the original message
|
|
104
|
-
let quotedContent = '';
|
|
105
|
-
if (original.payload) {
|
|
106
|
-
const originalBody = getNestedHistory(original.payload);
|
|
107
|
-
if (originalBody && fromHeader && dateHeader) {
|
|
108
|
-
quotedContent = `\n\nOn ${dateHeader} ${fromHeader} wrote:\n\n${originalBody}`;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
const fullBody = params.body + quotedContent;
|
|
112
|
-
// Build raw message
|
|
113
|
-
const msgHeaders = [];
|
|
114
|
-
if (to?.length) msgHeaders.push(`To: ${to.join(', ')}`);
|
|
115
|
-
if (cc?.length) msgHeaders.push(`Cc: ${cc.join(', ')}`);
|
|
116
|
-
if (params.bcc?.length) msgHeaders.push(`Bcc: ${params.bcc.join(', ')}`);
|
|
117
|
-
msgHeaders.push(`Subject: ${subject}`);
|
|
118
|
-
if (messageIdHeader) msgHeaders.push(`In-Reply-To: ${messageIdHeader}`);
|
|
119
|
-
if (references.length) msgHeaders.push(`References: ${references.join(' ')}`);
|
|
120
|
-
let raw;
|
|
121
|
-
if (params.attachments?.length) {
|
|
122
|
-
const boundary = `boundary_${Date.now()}_${Math.random().toString(36).slice(2)}`;
|
|
123
|
-
msgHeaders.push('MIME-Version: 1.0');
|
|
124
|
-
msgHeaders.push(`Content-Type: multipart/mixed; boundary="${boundary}"`);
|
|
125
|
-
const parts = [];
|
|
126
|
-
parts.push([
|
|
127
|
-
`--${boundary}`,
|
|
128
|
-
'Content-Type: text/plain; charset="UTF-8"',
|
|
129
|
-
'Content-Transfer-Encoding: base64',
|
|
130
|
-
'',
|
|
131
|
-
Buffer.from(fullBody).toString('base64'),
|
|
132
|
-
].join('\r\n'));
|
|
133
|
-
for (const att of params.attachments) {
|
|
134
|
-
parts.push([
|
|
135
|
-
`--${boundary}`,
|
|
136
|
-
`Content-Type: ${att.mimeType}; name="${att.filename}"`,
|
|
137
|
-
'Content-Transfer-Encoding: base64',
|
|
138
|
-
`Content-Disposition: attachment; filename="${att.filename}"`,
|
|
139
|
-
'',
|
|
140
|
-
att.base64Data,
|
|
141
|
-
].join('\r\n'));
|
|
142
|
-
}
|
|
143
|
-
const rawStr = [msgHeaders.join('\r\n'), '', parts.join('\r\n'), `--${boundary}--`].join('\r\n');
|
|
144
|
-
raw = Buffer.from(rawStr).toString('base64url').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
145
|
-
} else {
|
|
146
|
-
msgHeaders.push('Content-Type: text/plain; charset="UTF-8"');
|
|
147
|
-
msgHeaders.push('Content-Transfer-Encoding: quoted-printable');
|
|
148
|
-
msgHeaders.push('MIME-Version: 1.0');
|
|
149
|
-
const rawStr = [msgHeaders.join('\r\n'), '', wrapTextBody(fullBody)].join('\r\n');
|
|
150
|
-
raw = Buffer.from(rawStr).toString('base64url').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
151
|
-
}
|
|
152
|
-
const { data } = await gmail.users.messages.send({
|
|
153
|
-
userId: 'me',
|
|
154
|
-
requestBody: { raw, threadId },
|
|
155
|
-
});
|
|
156
|
-
if (data.payload) {
|
|
157
|
-
data.payload = processMessagePart(data.payload, params.includeBodyHtml);
|
|
158
|
-
}
|
|
159
|
-
return JSON.stringify(data);
|
|
160
|
-
},
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
server.addTool({
|
|
164
|
-
name: 'forward_message',
|
|
165
|
-
description: 'Forward a message to new recipients. Includes the original message body as quoted content and re-attaches any original attachments.',
|
|
166
|
-
parameters: z.object({
|
|
167
|
-
messageId: z.string().describe("The ID of the message to forward"),
|
|
168
|
-
to: z.array(z.string()).describe("Recipient email addresses to forward to"),
|
|
169
|
-
cc: z.array(z.string()).optional().describe("CC recipient email addresses"),
|
|
170
|
-
bcc: z.array(z.string()).optional().describe("BCC recipient email addresses"),
|
|
171
|
-
body: z.string().optional().describe("Optional commentary to prepend above the forwarded content"),
|
|
172
|
-
includeAttachments: z.boolean().optional().describe("Whether to include original attachments. Default: true"),
|
|
173
|
-
includeBodyHtml: z.boolean().optional().describe("Whether to include the parsed HTML in the return"),
|
|
174
|
-
}),
|
|
175
|
-
execute: async (params) => {
|
|
176
|
-
const gmail = await getGmailClient();
|
|
177
|
-
// Fetch original message
|
|
178
|
-
const { data: original } = await gmail.users.messages.get({ userId: 'me', id: params.messageId, format: 'full' });
|
|
179
|
-
const headers = original.payload?.headers || [];
|
|
180
|
-
const originalSubject = findHeader(headers, 'subject') || '';
|
|
181
|
-
const fromHeader = findHeader(headers, 'from') || '';
|
|
182
|
-
const dateHeader = findHeader(headers, 'date') || '';
|
|
183
|
-
const toHeader = findHeader(headers, 'to') || '';
|
|
184
|
-
// Build subject
|
|
185
|
-
let subject = originalSubject;
|
|
186
|
-
if (!subject.toLowerCase().startsWith('fwd:')) {
|
|
187
|
-
subject = `Fwd: ${subject}`;
|
|
188
|
-
}
|
|
189
|
-
// Build forwarded content
|
|
190
|
-
const originalBody = original.payload ? getPlainTextBody(original.payload) : '';
|
|
191
|
-
let forwardedContent = [
|
|
192
|
-
'---------- Forwarded message ---------',
|
|
193
|
-
`From: ${fromHeader}`,
|
|
194
|
-
`Date: ${dateHeader}`,
|
|
195
|
-
`Subject: ${originalSubject}`,
|
|
196
|
-
`To: ${toHeader}`,
|
|
197
|
-
'',
|
|
198
|
-
originalBody,
|
|
199
|
-
].join('\n');
|
|
200
|
-
const fullBody = (params.body ? params.body + '\n\n' : '') + forwardedContent;
|
|
201
|
-
// Collect original attachments
|
|
202
|
-
const attachments = [];
|
|
203
|
-
const includeAttachments = params.includeAttachments !== false;
|
|
204
|
-
if (includeAttachments && original.payload) {
|
|
205
|
-
const collectAttachments = (part) => {
|
|
206
|
-
if (part.filename && part.body?.attachmentId) {
|
|
207
|
-
attachments.push({
|
|
208
|
-
filename: part.filename,
|
|
209
|
-
mimeType: part.mimeType || 'application/octet-stream',
|
|
210
|
-
attachmentId: part.body.attachmentId,
|
|
211
|
-
});
|
|
212
|
-
}
|
|
213
|
-
if (part.parts) part.parts.forEach(collectAttachments);
|
|
214
|
-
};
|
|
215
|
-
collectAttachments(original.payload);
|
|
216
|
-
}
|
|
217
|
-
// Fetch attachment data
|
|
218
|
-
const attachmentParts = [];
|
|
219
|
-
for (const att of attachments) {
|
|
220
|
-
const { data: attData } = await gmail.users.messages.attachments.get({
|
|
221
|
-
userId: 'me', messageId: params.messageId, id: att.attachmentId,
|
|
222
|
-
});
|
|
223
|
-
attachmentParts.push({
|
|
224
|
-
filename: att.filename,
|
|
225
|
-
mimeType: att.mimeType,
|
|
226
|
-
base64Data: attData.data.replace(/-/g, '+').replace(/_/g, '/'),
|
|
227
|
-
});
|
|
228
|
-
}
|
|
229
|
-
// Build raw message
|
|
230
|
-
const msgHeaders = [];
|
|
231
|
-
msgHeaders.push(`To: ${params.to.join(', ')}`);
|
|
232
|
-
if (params.cc?.length) msgHeaders.push(`Cc: ${params.cc.join(', ')}`);
|
|
233
|
-
if (params.bcc?.length) msgHeaders.push(`Bcc: ${params.bcc.join(', ')}`);
|
|
234
|
-
msgHeaders.push(`Subject: ${subject}`);
|
|
235
|
-
let raw;
|
|
236
|
-
if (attachmentParts.length) {
|
|
237
|
-
const boundary = `boundary_${Date.now()}_${Math.random().toString(36).slice(2)}`;
|
|
238
|
-
msgHeaders.push('MIME-Version: 1.0');
|
|
239
|
-
msgHeaders.push(`Content-Type: multipart/mixed; boundary="${boundary}"`);
|
|
240
|
-
const parts = [];
|
|
241
|
-
parts.push([
|
|
242
|
-
`--${boundary}`,
|
|
243
|
-
'Content-Type: text/plain; charset="UTF-8"',
|
|
244
|
-
'Content-Transfer-Encoding: base64',
|
|
245
|
-
'',
|
|
246
|
-
Buffer.from(fullBody).toString('base64'),
|
|
247
|
-
].join('\r\n'));
|
|
248
|
-
for (const att of attachmentParts) {
|
|
249
|
-
parts.push([
|
|
250
|
-
`--${boundary}`,
|
|
251
|
-
`Content-Type: ${att.mimeType}; name="${att.filename}"`,
|
|
252
|
-
'Content-Transfer-Encoding: base64',
|
|
253
|
-
`Content-Disposition: attachment; filename="${att.filename}"`,
|
|
254
|
-
'',
|
|
255
|
-
att.base64Data,
|
|
256
|
-
].join('\r\n'));
|
|
257
|
-
}
|
|
258
|
-
const rawStr = [msgHeaders.join('\r\n'), '', parts.join('\r\n'), `--${boundary}--`].join('\r\n');
|
|
259
|
-
raw = Buffer.from(rawStr).toString('base64url').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
260
|
-
} else {
|
|
261
|
-
msgHeaders.push('Content-Type: text/plain; charset="UTF-8"');
|
|
262
|
-
msgHeaders.push('Content-Transfer-Encoding: quoted-printable');
|
|
263
|
-
msgHeaders.push('MIME-Version: 1.0');
|
|
264
|
-
const rawStr = [msgHeaders.join('\r\n'), '', wrapTextBody(fullBody)].join('\r\n');
|
|
265
|
-
raw = Buffer.from(rawStr).toString('base64url').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
266
|
-
}
|
|
267
|
-
const { data } = await gmail.users.messages.send({
|
|
268
|
-
userId: 'me',
|
|
269
|
-
requestBody: { raw },
|
|
270
|
-
});
|
|
271
|
-
if (data.payload) {
|
|
272
|
-
data.payload = processMessagePart(data.payload, params.includeBodyHtml);
|
|
273
|
-
}
|
|
274
|
-
return JSON.stringify(data);
|
|
275
|
-
},
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
server.addTool({
|
|
279
|
-
name: 'get_message',
|
|
280
|
-
description: 'Get a specific message by ID with format options',
|
|
281
|
-
parameters: z.object({
|
|
282
|
-
id: z.string().describe("The ID of the message to retrieve"),
|
|
283
|
-
includeBodyHtml: z.boolean().optional().describe("Whether to include the parsed HTML in the return for each body"),
|
|
284
|
-
}),
|
|
285
|
-
execute: async (params) => {
|
|
286
|
-
const gmail = await getGmailClient();
|
|
287
|
-
const { data } = await gmail.users.messages.get({ userId: 'me', id: params.id, format: 'full' });
|
|
288
|
-
if (data.payload) {
|
|
289
|
-
data.payload = processMessagePart(data.payload, params.includeBodyHtml);
|
|
290
|
-
}
|
|
291
|
-
return JSON.stringify(data);
|
|
292
|
-
},
|
|
293
|
-
});
|
|
294
|
-
|
|
295
|
-
server.addTool({
|
|
296
|
-
name: 'list_messages',
|
|
297
|
-
description: 'List messages in the user\'s mailbox with optional filtering',
|
|
298
|
-
parameters: z.object({
|
|
299
|
-
maxResults: z.number().optional().describe("Maximum number of messages to return (1-500)"),
|
|
300
|
-
pageToken: z.string().optional().describe("Page token to retrieve a specific page of results"),
|
|
301
|
-
q: z.string().optional().describe("Only return messages matching the specified query (same format as Gmail search box)"),
|
|
302
|
-
labelIds: z.array(z.string()).optional().describe("Only return messages with labels that match all specified label IDs"),
|
|
303
|
-
includeSpamTrash: z.boolean().optional().describe("Include messages from SPAM and TRASH"),
|
|
304
|
-
includeBodyHtml: z.boolean().optional().describe("Whether to include the parsed HTML in the return for each body"),
|
|
305
|
-
}),
|
|
306
|
-
execute: async (params) => {
|
|
307
|
-
const gmail = await getGmailClient();
|
|
308
|
-
const { data } = await gmail.users.messages.list({ userId: 'me', ...params });
|
|
309
|
-
if (data.messages) {
|
|
310
|
-
data.messages = data.messages.map(message => {
|
|
311
|
-
if (message.payload) {
|
|
312
|
-
message.payload = processMessagePart(message.payload, params.includeBodyHtml);
|
|
313
|
-
}
|
|
314
|
-
return message;
|
|
315
|
-
});
|
|
316
|
-
}
|
|
317
|
-
return JSON.stringify(data);
|
|
318
|
-
},
|
|
319
|
-
});
|
|
320
|
-
|
|
321
|
-
server.addTool({
|
|
322
|
-
name: 'modify_message',
|
|
323
|
-
description: 'Modify the labels on a message',
|
|
324
|
-
parameters: z.object({
|
|
325
|
-
id: z.string().describe("The ID of the message to modify"),
|
|
326
|
-
addLabelIds: z.array(z.string()).optional().describe("Label IDs to add"),
|
|
327
|
-
removeLabelIds: z.array(z.string()).optional().describe("Label IDs to remove"),
|
|
328
|
-
}),
|
|
329
|
-
execute: async (params) => {
|
|
330
|
-
const gmail = await getGmailClient();
|
|
331
|
-
const { data } = await gmail.users.messages.modify({
|
|
332
|
-
userId: 'me', id: params.id,
|
|
333
|
-
requestBody: { addLabelIds: params.addLabelIds, removeLabelIds: params.removeLabelIds }
|
|
334
|
-
});
|
|
335
|
-
return JSON.stringify(data);
|
|
336
|
-
},
|
|
337
|
-
});
|
|
338
|
-
|
|
339
|
-
server.addTool({
|
|
340
|
-
name: 'delete_message',
|
|
341
|
-
description: 'Immediately and permanently delete a message',
|
|
342
|
-
parameters: z.object({
|
|
343
|
-
id: z.string().describe("The ID of the message to delete"),
|
|
344
|
-
}),
|
|
345
|
-
execute: async (params) => {
|
|
346
|
-
const gmail = await getGmailClient();
|
|
347
|
-
const { data } = await gmail.users.messages.delete({ userId: 'me', id: params.id });
|
|
348
|
-
return JSON.stringify(data || { success: true });
|
|
349
|
-
},
|
|
350
|
-
});
|
|
351
|
-
|
|
352
|
-
server.addTool({
|
|
353
|
-
name: 'trash_message',
|
|
354
|
-
description: 'Move a message to the trash',
|
|
355
|
-
parameters: z.object({
|
|
356
|
-
id: z.string().describe("The ID of the message to move to trash"),
|
|
357
|
-
}),
|
|
358
|
-
execute: async (params) => {
|
|
359
|
-
const gmail = await getGmailClient();
|
|
360
|
-
const { data } = await gmail.users.messages.trash({ userId: 'me', id: params.id });
|
|
361
|
-
return JSON.stringify(data);
|
|
362
|
-
},
|
|
363
|
-
});
|
|
364
|
-
|
|
365
|
-
server.addTool({
|
|
366
|
-
name: 'untrash_message',
|
|
367
|
-
description: 'Remove a message from the trash',
|
|
368
|
-
parameters: z.object({
|
|
369
|
-
id: z.string().describe("The ID of the message to remove from trash"),
|
|
370
|
-
}),
|
|
371
|
-
execute: async (params) => {
|
|
372
|
-
const gmail = await getGmailClient();
|
|
373
|
-
const { data } = await gmail.users.messages.untrash({ userId: 'me', id: params.id });
|
|
374
|
-
return JSON.stringify(data);
|
|
375
|
-
},
|
|
376
|
-
});
|
|
377
|
-
|
|
378
|
-
server.addTool({
|
|
379
|
-
name: 'batch_delete_messages',
|
|
380
|
-
description: 'Delete multiple messages',
|
|
381
|
-
parameters: z.object({
|
|
382
|
-
ids: z.array(z.string()).describe("The IDs of the messages to delete"),
|
|
383
|
-
}),
|
|
384
|
-
execute: async (params) => {
|
|
385
|
-
const gmail = await getGmailClient();
|
|
386
|
-
const { data } = await gmail.users.messages.batchDelete({ userId: 'me', requestBody: { ids: params.ids } });
|
|
387
|
-
return JSON.stringify(data || { success: true });
|
|
388
|
-
},
|
|
389
|
-
});
|
|
390
|
-
|
|
391
|
-
server.addTool({
|
|
392
|
-
name: 'batch_modify_messages',
|
|
393
|
-
description: 'Modify the labels on multiple messages',
|
|
394
|
-
parameters: z.object({
|
|
395
|
-
ids: z.array(z.string()).describe("The IDs of the messages to modify"),
|
|
396
|
-
addLabelIds: z.array(z.string()).optional().describe("Label IDs to add"),
|
|
397
|
-
removeLabelIds: z.array(z.string()).optional().describe("Label IDs to remove"),
|
|
398
|
-
}),
|
|
399
|
-
execute: async (params) => {
|
|
400
|
-
const gmail = await getGmailClient();
|
|
401
|
-
const { data } = await gmail.users.messages.batchModify({
|
|
402
|
-
userId: 'me',
|
|
403
|
-
requestBody: { ids: params.ids, addLabelIds: params.addLabelIds, removeLabelIds: params.removeLabelIds }
|
|
404
|
-
});
|
|
405
|
-
return JSON.stringify(data || { success: true });
|
|
406
|
-
},
|
|
407
|
-
});
|
|
408
|
-
|
|
409
|
-
server.addTool({
|
|
410
|
-
name: 'batch_get_messages',
|
|
411
|
-
description: 'Get multiple messages by ID in parallel. More efficient than calling get_message multiple times.',
|
|
412
|
-
parameters: z.object({
|
|
413
|
-
ids: z.array(z.string()).describe("The IDs of the messages to retrieve"),
|
|
414
|
-
includeBodyHtml: z.boolean().optional().describe("Whether to include the parsed HTML in the return for each body"),
|
|
415
|
-
}),
|
|
416
|
-
execute: async (params) => {
|
|
417
|
-
const gmail = await getGmailClient();
|
|
418
|
-
const results = await Promise.all(
|
|
419
|
-
params.ids.map(async (id) => {
|
|
420
|
-
try {
|
|
421
|
-
const { data } = await gmail.users.messages.get({ userId: 'me', id, format: 'full' });
|
|
422
|
-
if (data.payload) {
|
|
423
|
-
data.payload = processMessagePart(data.payload, params.includeBodyHtml);
|
|
424
|
-
}
|
|
425
|
-
return data;
|
|
426
|
-
} catch (error) {
|
|
427
|
-
return { id, error: error.message || 'Failed to retrieve message' };
|
|
428
|
-
}
|
|
429
|
-
})
|
|
430
|
-
);
|
|
431
|
-
return JSON.stringify(results);
|
|
432
|
-
},
|
|
433
|
-
});
|
|
434
|
-
|
|
435
|
-
server.addTool({
|
|
436
|
-
name: 'get_attachment',
|
|
437
|
-
description: 'Get a message attachment',
|
|
438
|
-
parameters: z.object({
|
|
439
|
-
messageId: z.string().describe("ID of the message containing the attachment"),
|
|
440
|
-
id: z.string().describe("The ID of the attachment"),
|
|
441
|
-
}),
|
|
442
|
-
execute: async (params) => {
|
|
443
|
-
const gmail = await getGmailClient();
|
|
444
|
-
const { data } = await gmail.users.messages.attachments.get({ userId: 'me', messageId: params.messageId, id: params.id });
|
|
445
|
-
return JSON.stringify(data);
|
|
446
|
-
},
|
|
447
|
-
});
|
|
448
|
-
}
|
|
1
|
+
// Gmail Message tools
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getGmailClient } from '../../clients.js';
|
|
4
|
+
import { processMessagePart, constructRawMessage, constructRawMessageWithAttachments, findHeader, formatEmailList, getNestedHistory, getPlainTextBody, wrapTextBody } from '../../helpers.js';
|
|
5
|
+
|
|
6
|
+
export function register(server) {
|
|
7
|
+
server.addTool({
|
|
8
|
+
name: 'send_message',
|
|
9
|
+
description: 'Send an email message to specified recipients. Note the mechanics of the raw parameter.',
|
|
10
|
+
parameters: z.object({
|
|
11
|
+
raw: z.string().optional().describe("The entire email message in base64url encoded RFC 2822 format, ignores to, cc, bcc, subject, body if provided"),
|
|
12
|
+
threadId: z.string().optional().describe("The thread ID to associate this message with"),
|
|
13
|
+
to: z.array(z.string()).optional().describe("List of recipient email addresses"),
|
|
14
|
+
cc: z.array(z.string()).optional().describe("List of CC recipient email addresses"),
|
|
15
|
+
bcc: z.array(z.string()).optional().describe("List of BCC recipient email addresses"),
|
|
16
|
+
subject: z.string().optional().describe("The subject of the email"),
|
|
17
|
+
body: z.string().optional().describe("The body of the email"),
|
|
18
|
+
attachments: z.array(z.object({
|
|
19
|
+
filename: z.string().describe("Attachment file name"),
|
|
20
|
+
mimeType: z.string().describe("MIME type of the attachment"),
|
|
21
|
+
base64Data: z.string().describe("Base64 encoded attachment data"),
|
|
22
|
+
})).optional().describe("File attachments to include"),
|
|
23
|
+
includeBodyHtml: z.boolean().optional().describe("Whether to include the parsed HTML in the return for each body"),
|
|
24
|
+
}),
|
|
25
|
+
execute: async (params) => {
|
|
26
|
+
const gmail = await getGmailClient();
|
|
27
|
+
let raw = params.raw;
|
|
28
|
+
if (!raw) {
|
|
29
|
+
if (params.attachments?.length) {
|
|
30
|
+
raw = await constructRawMessageWithAttachments(gmail, params);
|
|
31
|
+
} else {
|
|
32
|
+
raw = await constructRawMessage(gmail, params);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const sendParams = { userId: 'me', requestBody: { raw } };
|
|
36
|
+
if (params.threadId && sendParams.requestBody) {
|
|
37
|
+
sendParams.requestBody.threadId = params.threadId;
|
|
38
|
+
}
|
|
39
|
+
const { data } = await gmail.users.messages.send(sendParams);
|
|
40
|
+
if (data.payload) {
|
|
41
|
+
data.payload = processMessagePart(data.payload, params.includeBodyHtml);
|
|
42
|
+
}
|
|
43
|
+
return JSON.stringify(data);
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
server.addTool({
|
|
48
|
+
name: 'reply_message',
|
|
49
|
+
description: 'Reply to a message. Automatically handles To/Cc recipients, subject prefix, threading headers, and quoted content. Use replyAll to include all original recipients.',
|
|
50
|
+
parameters: z.object({
|
|
51
|
+
messageId: z.string().describe("The ID of the message to reply to"),
|
|
52
|
+
body: z.string().describe("The reply body text"),
|
|
53
|
+
replyAll: z.boolean().optional().describe("If true, reply to all original recipients (To + Cc minus yourself). Default: false"),
|
|
54
|
+
to: z.array(z.string()).optional().describe("Override recipient list (if omitted, replies to sender or Reply-To)"),
|
|
55
|
+
cc: z.array(z.string()).optional().describe("Override CC list (if omitted and replyAll, uses original To + Cc minus yourself)"),
|
|
56
|
+
bcc: z.array(z.string()).optional().describe("Optional BCC recipients"),
|
|
57
|
+
attachments: z.array(z.object({
|
|
58
|
+
filename: z.string().describe("Attachment file name"),
|
|
59
|
+
mimeType: z.string().describe("MIME type of the attachment"),
|
|
60
|
+
base64Data: z.string().describe("Base64 encoded attachment data"),
|
|
61
|
+
})).optional().describe("Optional attachments to include in the reply"),
|
|
62
|
+
includeBodyHtml: z.boolean().optional().describe("Whether to include the parsed HTML in the return"),
|
|
63
|
+
}),
|
|
64
|
+
execute: async (params) => {
|
|
65
|
+
const gmail = await getGmailClient();
|
|
66
|
+
// Fetch original message
|
|
67
|
+
const { data: original } = await gmail.users.messages.get({ userId: 'me', id: params.messageId, format: 'full' });
|
|
68
|
+
const headers = original.payload?.headers || [];
|
|
69
|
+
const threadId = original.threadId;
|
|
70
|
+
// Get sender info for quoted content
|
|
71
|
+
const fromHeader = findHeader(headers, 'reply-to') || findHeader(headers, 'from');
|
|
72
|
+
const dateHeader = findHeader(headers, 'date');
|
|
73
|
+
const originalSubject = findHeader(headers, 'subject') || '';
|
|
74
|
+
const messageIdHeader = findHeader(headers, 'message-id');
|
|
75
|
+
const referencesHeader = findHeader(headers, 'references');
|
|
76
|
+
// Build subject
|
|
77
|
+
let subject = originalSubject;
|
|
78
|
+
if (!subject.toLowerCase().startsWith('re:')) {
|
|
79
|
+
subject = `Re: ${subject}`;
|
|
80
|
+
}
|
|
81
|
+
// Build recipients
|
|
82
|
+
let to = params.to;
|
|
83
|
+
let cc = params.cc;
|
|
84
|
+
if (!to) {
|
|
85
|
+
to = fromHeader ? [fromHeader] : [];
|
|
86
|
+
}
|
|
87
|
+
if (!cc && params.replyAll) {
|
|
88
|
+
// Get own email for exclusion
|
|
89
|
+
const { data: profile } = await gmail.users.getProfile({ userId: 'me' });
|
|
90
|
+
const myEmail = profile.emailAddress?.toLowerCase();
|
|
91
|
+
const originalTo = formatEmailList(findHeader(headers, 'to'));
|
|
92
|
+
const originalCc = formatEmailList(findHeader(headers, 'cc'));
|
|
93
|
+
const allRecipients = [...originalTo, ...originalCc].filter(
|
|
94
|
+
email => !email.toLowerCase().includes(myEmail)
|
|
95
|
+
);
|
|
96
|
+
// Remove the sender from CC since they're in To
|
|
97
|
+
cc = allRecipients.filter(email => !to.some(t => email.toLowerCase().includes(t.toLowerCase())));
|
|
98
|
+
}
|
|
99
|
+
// Build threading headers
|
|
100
|
+
const references = [];
|
|
101
|
+
if (referencesHeader) references.push(...referencesHeader.split(' '));
|
|
102
|
+
if (messageIdHeader) references.push(messageIdHeader);
|
|
103
|
+
// Build quoted content from the original message
|
|
104
|
+
let quotedContent = '';
|
|
105
|
+
if (original.payload) {
|
|
106
|
+
const originalBody = getNestedHistory(original.payload);
|
|
107
|
+
if (originalBody && fromHeader && dateHeader) {
|
|
108
|
+
quotedContent = `\n\nOn ${dateHeader} ${fromHeader} wrote:\n\n${originalBody}`;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const fullBody = params.body + quotedContent;
|
|
112
|
+
// Build raw message
|
|
113
|
+
const msgHeaders = [];
|
|
114
|
+
if (to?.length) msgHeaders.push(`To: ${to.join(', ')}`);
|
|
115
|
+
if (cc?.length) msgHeaders.push(`Cc: ${cc.join(', ')}`);
|
|
116
|
+
if (params.bcc?.length) msgHeaders.push(`Bcc: ${params.bcc.join(', ')}`);
|
|
117
|
+
msgHeaders.push(`Subject: ${subject}`);
|
|
118
|
+
if (messageIdHeader) msgHeaders.push(`In-Reply-To: ${messageIdHeader}`);
|
|
119
|
+
if (references.length) msgHeaders.push(`References: ${references.join(' ')}`);
|
|
120
|
+
let raw;
|
|
121
|
+
if (params.attachments?.length) {
|
|
122
|
+
const boundary = `boundary_${Date.now()}_${Math.random().toString(36).slice(2)}`;
|
|
123
|
+
msgHeaders.push('MIME-Version: 1.0');
|
|
124
|
+
msgHeaders.push(`Content-Type: multipart/mixed; boundary="${boundary}"`);
|
|
125
|
+
const parts = [];
|
|
126
|
+
parts.push([
|
|
127
|
+
`--${boundary}`,
|
|
128
|
+
'Content-Type: text/plain; charset="UTF-8"',
|
|
129
|
+
'Content-Transfer-Encoding: base64',
|
|
130
|
+
'',
|
|
131
|
+
Buffer.from(fullBody).toString('base64'),
|
|
132
|
+
].join('\r\n'));
|
|
133
|
+
for (const att of params.attachments) {
|
|
134
|
+
parts.push([
|
|
135
|
+
`--${boundary}`,
|
|
136
|
+
`Content-Type: ${att.mimeType}; name="${att.filename}"`,
|
|
137
|
+
'Content-Transfer-Encoding: base64',
|
|
138
|
+
`Content-Disposition: attachment; filename="${att.filename}"`,
|
|
139
|
+
'',
|
|
140
|
+
att.base64Data,
|
|
141
|
+
].join('\r\n'));
|
|
142
|
+
}
|
|
143
|
+
const rawStr = [msgHeaders.join('\r\n'), '', parts.join('\r\n'), `--${boundary}--`].join('\r\n');
|
|
144
|
+
raw = Buffer.from(rawStr).toString('base64url').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
145
|
+
} else {
|
|
146
|
+
msgHeaders.push('Content-Type: text/plain; charset="UTF-8"');
|
|
147
|
+
msgHeaders.push('Content-Transfer-Encoding: quoted-printable');
|
|
148
|
+
msgHeaders.push('MIME-Version: 1.0');
|
|
149
|
+
const rawStr = [msgHeaders.join('\r\n'), '', wrapTextBody(fullBody)].join('\r\n');
|
|
150
|
+
raw = Buffer.from(rawStr).toString('base64url').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
151
|
+
}
|
|
152
|
+
const { data } = await gmail.users.messages.send({
|
|
153
|
+
userId: 'me',
|
|
154
|
+
requestBody: { raw, threadId },
|
|
155
|
+
});
|
|
156
|
+
if (data.payload) {
|
|
157
|
+
data.payload = processMessagePart(data.payload, params.includeBodyHtml);
|
|
158
|
+
}
|
|
159
|
+
return JSON.stringify(data);
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
server.addTool({
|
|
164
|
+
name: 'forward_message',
|
|
165
|
+
description: 'Forward a message to new recipients. Includes the original message body as quoted content and re-attaches any original attachments.',
|
|
166
|
+
parameters: z.object({
|
|
167
|
+
messageId: z.string().describe("The ID of the message to forward"),
|
|
168
|
+
to: z.array(z.string()).describe("Recipient email addresses to forward to"),
|
|
169
|
+
cc: z.array(z.string()).optional().describe("CC recipient email addresses"),
|
|
170
|
+
bcc: z.array(z.string()).optional().describe("BCC recipient email addresses"),
|
|
171
|
+
body: z.string().optional().describe("Optional commentary to prepend above the forwarded content"),
|
|
172
|
+
includeAttachments: z.boolean().optional().describe("Whether to include original attachments. Default: true"),
|
|
173
|
+
includeBodyHtml: z.boolean().optional().describe("Whether to include the parsed HTML in the return"),
|
|
174
|
+
}),
|
|
175
|
+
execute: async (params) => {
|
|
176
|
+
const gmail = await getGmailClient();
|
|
177
|
+
// Fetch original message
|
|
178
|
+
const { data: original } = await gmail.users.messages.get({ userId: 'me', id: params.messageId, format: 'full' });
|
|
179
|
+
const headers = original.payload?.headers || [];
|
|
180
|
+
const originalSubject = findHeader(headers, 'subject') || '';
|
|
181
|
+
const fromHeader = findHeader(headers, 'from') || '';
|
|
182
|
+
const dateHeader = findHeader(headers, 'date') || '';
|
|
183
|
+
const toHeader = findHeader(headers, 'to') || '';
|
|
184
|
+
// Build subject
|
|
185
|
+
let subject = originalSubject;
|
|
186
|
+
if (!subject.toLowerCase().startsWith('fwd:')) {
|
|
187
|
+
subject = `Fwd: ${subject}`;
|
|
188
|
+
}
|
|
189
|
+
// Build forwarded content
|
|
190
|
+
const originalBody = original.payload ? getPlainTextBody(original.payload) : '';
|
|
191
|
+
let forwardedContent = [
|
|
192
|
+
'---------- Forwarded message ---------',
|
|
193
|
+
`From: ${fromHeader}`,
|
|
194
|
+
`Date: ${dateHeader}`,
|
|
195
|
+
`Subject: ${originalSubject}`,
|
|
196
|
+
`To: ${toHeader}`,
|
|
197
|
+
'',
|
|
198
|
+
originalBody,
|
|
199
|
+
].join('\n');
|
|
200
|
+
const fullBody = (params.body ? params.body + '\n\n' : '') + forwardedContent;
|
|
201
|
+
// Collect original attachments
|
|
202
|
+
const attachments = [];
|
|
203
|
+
const includeAttachments = params.includeAttachments !== false;
|
|
204
|
+
if (includeAttachments && original.payload) {
|
|
205
|
+
const collectAttachments = (part) => {
|
|
206
|
+
if (part.filename && part.body?.attachmentId) {
|
|
207
|
+
attachments.push({
|
|
208
|
+
filename: part.filename,
|
|
209
|
+
mimeType: part.mimeType || 'application/octet-stream',
|
|
210
|
+
attachmentId: part.body.attachmentId,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
if (part.parts) part.parts.forEach(collectAttachments);
|
|
214
|
+
};
|
|
215
|
+
collectAttachments(original.payload);
|
|
216
|
+
}
|
|
217
|
+
// Fetch attachment data
|
|
218
|
+
const attachmentParts = [];
|
|
219
|
+
for (const att of attachments) {
|
|
220
|
+
const { data: attData } = await gmail.users.messages.attachments.get({
|
|
221
|
+
userId: 'me', messageId: params.messageId, id: att.attachmentId,
|
|
222
|
+
});
|
|
223
|
+
attachmentParts.push({
|
|
224
|
+
filename: att.filename,
|
|
225
|
+
mimeType: att.mimeType,
|
|
226
|
+
base64Data: attData.data.replace(/-/g, '+').replace(/_/g, '/'),
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
// Build raw message
|
|
230
|
+
const msgHeaders = [];
|
|
231
|
+
msgHeaders.push(`To: ${params.to.join(', ')}`);
|
|
232
|
+
if (params.cc?.length) msgHeaders.push(`Cc: ${params.cc.join(', ')}`);
|
|
233
|
+
if (params.bcc?.length) msgHeaders.push(`Bcc: ${params.bcc.join(', ')}`);
|
|
234
|
+
msgHeaders.push(`Subject: ${subject}`);
|
|
235
|
+
let raw;
|
|
236
|
+
if (attachmentParts.length) {
|
|
237
|
+
const boundary = `boundary_${Date.now()}_${Math.random().toString(36).slice(2)}`;
|
|
238
|
+
msgHeaders.push('MIME-Version: 1.0');
|
|
239
|
+
msgHeaders.push(`Content-Type: multipart/mixed; boundary="${boundary}"`);
|
|
240
|
+
const parts = [];
|
|
241
|
+
parts.push([
|
|
242
|
+
`--${boundary}`,
|
|
243
|
+
'Content-Type: text/plain; charset="UTF-8"',
|
|
244
|
+
'Content-Transfer-Encoding: base64',
|
|
245
|
+
'',
|
|
246
|
+
Buffer.from(fullBody).toString('base64'),
|
|
247
|
+
].join('\r\n'));
|
|
248
|
+
for (const att of attachmentParts) {
|
|
249
|
+
parts.push([
|
|
250
|
+
`--${boundary}`,
|
|
251
|
+
`Content-Type: ${att.mimeType}; name="${att.filename}"`,
|
|
252
|
+
'Content-Transfer-Encoding: base64',
|
|
253
|
+
`Content-Disposition: attachment; filename="${att.filename}"`,
|
|
254
|
+
'',
|
|
255
|
+
att.base64Data,
|
|
256
|
+
].join('\r\n'));
|
|
257
|
+
}
|
|
258
|
+
const rawStr = [msgHeaders.join('\r\n'), '', parts.join('\r\n'), `--${boundary}--`].join('\r\n');
|
|
259
|
+
raw = Buffer.from(rawStr).toString('base64url').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
260
|
+
} else {
|
|
261
|
+
msgHeaders.push('Content-Type: text/plain; charset="UTF-8"');
|
|
262
|
+
msgHeaders.push('Content-Transfer-Encoding: quoted-printable');
|
|
263
|
+
msgHeaders.push('MIME-Version: 1.0');
|
|
264
|
+
const rawStr = [msgHeaders.join('\r\n'), '', wrapTextBody(fullBody)].join('\r\n');
|
|
265
|
+
raw = Buffer.from(rawStr).toString('base64url').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
266
|
+
}
|
|
267
|
+
const { data } = await gmail.users.messages.send({
|
|
268
|
+
userId: 'me',
|
|
269
|
+
requestBody: { raw },
|
|
270
|
+
});
|
|
271
|
+
if (data.payload) {
|
|
272
|
+
data.payload = processMessagePart(data.payload, params.includeBodyHtml);
|
|
273
|
+
}
|
|
274
|
+
return JSON.stringify(data);
|
|
275
|
+
},
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
server.addTool({
|
|
279
|
+
name: 'get_message',
|
|
280
|
+
description: 'Get a specific message by ID with format options',
|
|
281
|
+
parameters: z.object({
|
|
282
|
+
id: z.string().describe("The ID of the message to retrieve"),
|
|
283
|
+
includeBodyHtml: z.boolean().optional().describe("Whether to include the parsed HTML in the return for each body"),
|
|
284
|
+
}),
|
|
285
|
+
execute: async (params) => {
|
|
286
|
+
const gmail = await getGmailClient();
|
|
287
|
+
const { data } = await gmail.users.messages.get({ userId: 'me', id: params.id, format: 'full' });
|
|
288
|
+
if (data.payload) {
|
|
289
|
+
data.payload = processMessagePart(data.payload, params.includeBodyHtml);
|
|
290
|
+
}
|
|
291
|
+
return JSON.stringify(data);
|
|
292
|
+
},
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
server.addTool({
|
|
296
|
+
name: 'list_messages',
|
|
297
|
+
description: 'List messages in the user\'s mailbox with optional filtering',
|
|
298
|
+
parameters: z.object({
|
|
299
|
+
maxResults: z.number().optional().describe("Maximum number of messages to return (1-500)"),
|
|
300
|
+
pageToken: z.string().optional().describe("Page token to retrieve a specific page of results"),
|
|
301
|
+
q: z.string().optional().describe("Only return messages matching the specified query (same format as Gmail search box)"),
|
|
302
|
+
labelIds: z.array(z.string()).optional().describe("Only return messages with labels that match all specified label IDs"),
|
|
303
|
+
includeSpamTrash: z.boolean().optional().describe("Include messages from SPAM and TRASH"),
|
|
304
|
+
includeBodyHtml: z.boolean().optional().describe("Whether to include the parsed HTML in the return for each body"),
|
|
305
|
+
}),
|
|
306
|
+
execute: async (params) => {
|
|
307
|
+
const gmail = await getGmailClient();
|
|
308
|
+
const { data } = await gmail.users.messages.list({ userId: 'me', ...params });
|
|
309
|
+
if (data.messages) {
|
|
310
|
+
data.messages = data.messages.map(message => {
|
|
311
|
+
if (message.payload) {
|
|
312
|
+
message.payload = processMessagePart(message.payload, params.includeBodyHtml);
|
|
313
|
+
}
|
|
314
|
+
return message;
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
return JSON.stringify(data);
|
|
318
|
+
},
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
server.addTool({
|
|
322
|
+
name: 'modify_message',
|
|
323
|
+
description: 'Modify the labels on a message',
|
|
324
|
+
parameters: z.object({
|
|
325
|
+
id: z.string().describe("The ID of the message to modify"),
|
|
326
|
+
addLabelIds: z.array(z.string()).optional().describe("Label IDs to add"),
|
|
327
|
+
removeLabelIds: z.array(z.string()).optional().describe("Label IDs to remove"),
|
|
328
|
+
}),
|
|
329
|
+
execute: async (params) => {
|
|
330
|
+
const gmail = await getGmailClient();
|
|
331
|
+
const { data } = await gmail.users.messages.modify({
|
|
332
|
+
userId: 'me', id: params.id,
|
|
333
|
+
requestBody: { addLabelIds: params.addLabelIds, removeLabelIds: params.removeLabelIds }
|
|
334
|
+
});
|
|
335
|
+
return JSON.stringify(data);
|
|
336
|
+
},
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
server.addTool({
|
|
340
|
+
name: 'delete_message',
|
|
341
|
+
description: 'Immediately and permanently delete a message',
|
|
342
|
+
parameters: z.object({
|
|
343
|
+
id: z.string().describe("The ID of the message to delete"),
|
|
344
|
+
}),
|
|
345
|
+
execute: async (params) => {
|
|
346
|
+
const gmail = await getGmailClient();
|
|
347
|
+
const { data } = await gmail.users.messages.delete({ userId: 'me', id: params.id });
|
|
348
|
+
return JSON.stringify(data || { success: true });
|
|
349
|
+
},
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
server.addTool({
|
|
353
|
+
name: 'trash_message',
|
|
354
|
+
description: 'Move a message to the trash',
|
|
355
|
+
parameters: z.object({
|
|
356
|
+
id: z.string().describe("The ID of the message to move to trash"),
|
|
357
|
+
}),
|
|
358
|
+
execute: async (params) => {
|
|
359
|
+
const gmail = await getGmailClient();
|
|
360
|
+
const { data } = await gmail.users.messages.trash({ userId: 'me', id: params.id });
|
|
361
|
+
return JSON.stringify(data);
|
|
362
|
+
},
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
server.addTool({
|
|
366
|
+
name: 'untrash_message',
|
|
367
|
+
description: 'Remove a message from the trash',
|
|
368
|
+
parameters: z.object({
|
|
369
|
+
id: z.string().describe("The ID of the message to remove from trash"),
|
|
370
|
+
}),
|
|
371
|
+
execute: async (params) => {
|
|
372
|
+
const gmail = await getGmailClient();
|
|
373
|
+
const { data } = await gmail.users.messages.untrash({ userId: 'me', id: params.id });
|
|
374
|
+
return JSON.stringify(data);
|
|
375
|
+
},
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
server.addTool({
|
|
379
|
+
name: 'batch_delete_messages',
|
|
380
|
+
description: 'Delete multiple messages',
|
|
381
|
+
parameters: z.object({
|
|
382
|
+
ids: z.array(z.string()).describe("The IDs of the messages to delete"),
|
|
383
|
+
}),
|
|
384
|
+
execute: async (params) => {
|
|
385
|
+
const gmail = await getGmailClient();
|
|
386
|
+
const { data } = await gmail.users.messages.batchDelete({ userId: 'me', requestBody: { ids: params.ids } });
|
|
387
|
+
return JSON.stringify(data || { success: true });
|
|
388
|
+
},
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
server.addTool({
|
|
392
|
+
name: 'batch_modify_messages',
|
|
393
|
+
description: 'Modify the labels on multiple messages',
|
|
394
|
+
parameters: z.object({
|
|
395
|
+
ids: z.array(z.string()).describe("The IDs of the messages to modify"),
|
|
396
|
+
addLabelIds: z.array(z.string()).optional().describe("Label IDs to add"),
|
|
397
|
+
removeLabelIds: z.array(z.string()).optional().describe("Label IDs to remove"),
|
|
398
|
+
}),
|
|
399
|
+
execute: async (params) => {
|
|
400
|
+
const gmail = await getGmailClient();
|
|
401
|
+
const { data } = await gmail.users.messages.batchModify({
|
|
402
|
+
userId: 'me',
|
|
403
|
+
requestBody: { ids: params.ids, addLabelIds: params.addLabelIds, removeLabelIds: params.removeLabelIds }
|
|
404
|
+
});
|
|
405
|
+
return JSON.stringify(data || { success: true });
|
|
406
|
+
},
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
server.addTool({
|
|
410
|
+
name: 'batch_get_messages',
|
|
411
|
+
description: 'Get multiple messages by ID in parallel. More efficient than calling get_message multiple times.',
|
|
412
|
+
parameters: z.object({
|
|
413
|
+
ids: z.array(z.string()).describe("The IDs of the messages to retrieve"),
|
|
414
|
+
includeBodyHtml: z.boolean().optional().describe("Whether to include the parsed HTML in the return for each body"),
|
|
415
|
+
}),
|
|
416
|
+
execute: async (params) => {
|
|
417
|
+
const gmail = await getGmailClient();
|
|
418
|
+
const results = await Promise.all(
|
|
419
|
+
params.ids.map(async (id) => {
|
|
420
|
+
try {
|
|
421
|
+
const { data } = await gmail.users.messages.get({ userId: 'me', id, format: 'full' });
|
|
422
|
+
if (data.payload) {
|
|
423
|
+
data.payload = processMessagePart(data.payload, params.includeBodyHtml);
|
|
424
|
+
}
|
|
425
|
+
return data;
|
|
426
|
+
} catch (error) {
|
|
427
|
+
return { id, error: error.message || 'Failed to retrieve message' };
|
|
428
|
+
}
|
|
429
|
+
})
|
|
430
|
+
);
|
|
431
|
+
return JSON.stringify(results);
|
|
432
|
+
},
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
server.addTool({
|
|
436
|
+
name: 'get_attachment',
|
|
437
|
+
description: 'Get a message attachment',
|
|
438
|
+
parameters: z.object({
|
|
439
|
+
messageId: z.string().describe("ID of the message containing the attachment"),
|
|
440
|
+
id: z.string().describe("The ID of the attachment"),
|
|
441
|
+
}),
|
|
442
|
+
execute: async (params) => {
|
|
443
|
+
const gmail = await getGmailClient();
|
|
444
|
+
const { data } = await gmail.users.messages.attachments.get({ userId: 'me', messageId: params.messageId, id: params.id });
|
|
445
|
+
return JSON.stringify(data);
|
|
446
|
+
},
|
|
447
|
+
});
|
|
448
|
+
}
|