mcp-google-multi 4.2.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.
@@ -0,0 +1,651 @@
1
+ import { z } from 'zod';
2
+ import { google } from 'googleapis';
3
+ import { ACCOUNTS } from '../accounts.js';
4
+ import { getClient } from '../client.js';
5
+ import { handleGoogleApiError } from './_errors.js';
6
+ import { encodeAddressHeader, encodeHeaderValue, normalizeBodyLineEndings } from './gmail-mime.js';
7
+ import * as path from 'path';
8
+ import * as fs from 'fs';
9
+ const accountEnum = z.enum(ACCOUNTS);
10
+ function getHeader(headers, name) {
11
+ return headers?.find((h) => h.name?.toLowerCase() === name.toLowerCase())?.value ?? '';
12
+ }
13
+ function decodeBody(payload) {
14
+ if (payload.body?.data) {
15
+ return Buffer.from(payload.body.data, 'base64url').toString('utf-8');
16
+ }
17
+ if (payload.parts) {
18
+ for (const part of payload.parts) {
19
+ if (part.mimeType === 'text/plain' && part.body?.data) {
20
+ return Buffer.from(part.body.data, 'base64url').toString('utf-8');
21
+ }
22
+ }
23
+ for (const part of payload.parts) {
24
+ if (part.mimeType === 'text/html' && part.body?.data) {
25
+ return Buffer.from(part.body.data, 'base64url').toString('utf-8');
26
+ }
27
+ }
28
+ for (const part of payload.parts) {
29
+ if (part.parts) {
30
+ const result = decodeBody(part);
31
+ if (result)
32
+ return result;
33
+ }
34
+ }
35
+ }
36
+ return '';
37
+ }
38
+ function getAttachments(payload) {
39
+ const attachments = [];
40
+ if (payload.filename && payload.body?.attachmentId) {
41
+ attachments.push({
42
+ filename: payload.filename,
43
+ attachmentId: payload.body.attachmentId,
44
+ mimeType: payload.mimeType ?? 'application/octet-stream',
45
+ });
46
+ }
47
+ if (payload.parts) {
48
+ for (const part of payload.parts) {
49
+ attachments.push(...getAttachments(part));
50
+ }
51
+ }
52
+ return attachments;
53
+ }
54
+ function parseMessage(msg) {
55
+ const headers = msg.payload?.headers ?? [];
56
+ return {
57
+ id: msg.id ?? '',
58
+ threadId: msg.threadId ?? '',
59
+ subject: getHeader(headers, 'Subject'),
60
+ from: getHeader(headers, 'From'),
61
+ to: getHeader(headers, 'To'),
62
+ cc: getHeader(headers, 'Cc'),
63
+ date: getHeader(headers, 'Date'),
64
+ body: decodeBody(msg.payload),
65
+ attachments: getAttachments(msg.payload),
66
+ };
67
+ }
68
+ export function registerGmailTools(server) {
69
+ server.registerTool('gmail_search', {
70
+ description: 'Search messages in a Gmail account',
71
+ inputSchema: {
72
+ account: accountEnum.describe('Google account alias'),
73
+ query: z.string().describe('Gmail search syntax, e.g. "from:monaam is:unread"'),
74
+ maxResults: z.number().min(1).max(100).default(20).optional()
75
+ .describe('Max results to return (default: 20, max: 100)'),
76
+ },
77
+ }, async ({ account, query, maxResults }) => {
78
+ try {
79
+ const auth = await getClient(account);
80
+ const gmail = google.gmail({ version: 'v1', auth });
81
+ const listRes = await gmail.users.messages.list({
82
+ userId: 'me',
83
+ q: query,
84
+ maxResults: maxResults ?? 20,
85
+ });
86
+ const messages = listRes.data.messages ?? [];
87
+ const results = [];
88
+ for (const m of messages) {
89
+ const detail = await gmail.users.messages.get({
90
+ userId: 'me',
91
+ id: m.id,
92
+ format: 'metadata',
93
+ metadataHeaders: ['From', 'Subject', 'Date'],
94
+ });
95
+ results.push({
96
+ id: detail.data.id ?? '',
97
+ threadId: detail.data.threadId ?? '',
98
+ subject: getHeader(detail.data.payload?.headers, 'Subject'),
99
+ from: getHeader(detail.data.payload?.headers, 'From'),
100
+ date: getHeader(detail.data.payload?.headers, 'Date'),
101
+ snippet: detail.data.snippet ?? '',
102
+ });
103
+ }
104
+ return {
105
+ content: [{ type: 'text', text: JSON.stringify(results, null, 2) }],
106
+ };
107
+ }
108
+ catch (error) {
109
+ return handleGmailError(error, account);
110
+ }
111
+ });
112
+ server.registerTool('gmail_read', {
113
+ description: 'Read a full Gmail message by ID',
114
+ inputSchema: {
115
+ account: accountEnum.describe('Google account alias'),
116
+ messageId: z.string().describe('Gmail message ID'),
117
+ },
118
+ }, async ({ account, messageId }) => {
119
+ try {
120
+ const auth = await getClient(account);
121
+ const gmail = google.gmail({ version: 'v1', auth });
122
+ const res = await gmail.users.messages.get({
123
+ userId: 'me',
124
+ id: messageId,
125
+ format: 'full',
126
+ });
127
+ const result = parseMessage(res.data);
128
+ return {
129
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
130
+ };
131
+ }
132
+ catch (error) {
133
+ return handleGmailError(error, account);
134
+ }
135
+ });
136
+ server.registerTool('gmail_read_thread', {
137
+ description: 'Read all messages in a Gmail thread',
138
+ inputSchema: {
139
+ account: accountEnum.describe('Google account alias'),
140
+ threadId: z.string().describe('Gmail thread ID'),
141
+ },
142
+ }, async ({ account, threadId }) => {
143
+ try {
144
+ const auth = await getClient(account);
145
+ const gmail = google.gmail({ version: 'v1', auth });
146
+ const res = await gmail.users.threads.get({
147
+ userId: 'me',
148
+ id: threadId,
149
+ format: 'full',
150
+ });
151
+ const messages = (res.data.messages ?? []).map(parseMessage);
152
+ return {
153
+ content: [{ type: 'text', text: JSON.stringify(messages, null, 2) }],
154
+ };
155
+ }
156
+ catch (error) {
157
+ return handleGmailError(error, account);
158
+ }
159
+ });
160
+ server.registerTool('gmail_send', {
161
+ description: 'Send an email from a Gmail account',
162
+ inputSchema: {
163
+ account: accountEnum.describe('Google account alias'),
164
+ to: z.string().describe('Recipient(s), comma-separated'),
165
+ subject: z.string().describe('Email subject'),
166
+ body: z.string().describe('Plain text body'),
167
+ cc: z.string().optional().describe('CC recipients, comma-separated'),
168
+ replyToMessageId: z.string().optional()
169
+ .describe('Message ID to reply to (sets In-Reply-To and References headers)'),
170
+ replyToThreadId: z.string().optional()
171
+ .describe('Thread ID to send the message in'),
172
+ },
173
+ }, async ({ account, to, subject, body, cc, replyToMessageId, replyToThreadId }) => {
174
+ try {
175
+ const auth = await getClient(account);
176
+ const gmail = google.gmail({ version: 'v1', auth });
177
+ const config = (await import('../accounts.js')).ACCOUNT_CONFIG[account];
178
+ const headers = [
179
+ `From: ${encodeAddressHeader(config.email)}`,
180
+ `To: ${encodeAddressHeader(to)}`,
181
+ `Subject: ${encodeHeaderValue(subject)}`,
182
+ 'MIME-Version: 1.0',
183
+ 'Content-Type: text/plain; charset="UTF-8"',
184
+ 'Content-Transfer-Encoding: 8bit',
185
+ ];
186
+ if (cc)
187
+ headers.push(`Cc: ${encodeAddressHeader(cc)}`);
188
+ if (replyToMessageId) {
189
+ headers.push(`In-Reply-To: ${replyToMessageId}`);
190
+ headers.push(`References: ${replyToMessageId}`);
191
+ }
192
+ const rawMessage = [...headers, '', normalizeBodyLineEndings(body)].join('\r\n');
193
+ const encoded = Buffer.from(rawMessage, 'utf-8').toString('base64url');
194
+ const sendParams = {
195
+ userId: 'me',
196
+ requestBody: { raw: encoded },
197
+ };
198
+ if (replyToThreadId) {
199
+ sendParams.requestBody.threadId = replyToThreadId;
200
+ }
201
+ const res = await gmail.users.messages.send(sendParams);
202
+ return {
203
+ content: [{
204
+ type: 'text',
205
+ text: JSON.stringify({ id: res.data.id, threadId: res.data.threadId }, null, 2),
206
+ }],
207
+ };
208
+ }
209
+ catch (error) {
210
+ return handleGmailError(error, account);
211
+ }
212
+ });
213
+ server.registerTool('gmail_download_attachment', {
214
+ description: 'Download an email attachment to local disk. Use gmail_read first to get the attachmentId.',
215
+ inputSchema: {
216
+ account: accountEnum.describe('Google account alias'),
217
+ messageId: z.string().describe('The Gmail message ID'),
218
+ attachmentId: z.string().describe('The attachment ID from gmail_read response'),
219
+ filename: z.string().describe('Filename to save as (e.g. report.xlsx)'),
220
+ savePath: z.string().describe('Absolute directory path to save into, e.g. /home/user/Downloads'),
221
+ },
222
+ }, async ({ account, messageId, attachmentId, filename, savePath }) => {
223
+ try {
224
+ const auth = await getClient(account);
225
+ const gmail = google.gmail({ version: 'v1', auth });
226
+ const res = await gmail.users.messages.attachments.get({
227
+ userId: 'me',
228
+ messageId,
229
+ id: attachmentId,
230
+ });
231
+ const data = res.data.data;
232
+ if (!data)
233
+ throw new Error('No attachment data returned');
234
+ const buffer = Buffer.from(data, 'base64url');
235
+ // Strip path components so callers can't escape savePath via "../".
236
+ const fullPath = path.join(savePath, path.basename(filename));
237
+ await fs.promises.writeFile(fullPath, buffer);
238
+ return {
239
+ content: [{ type: 'text', text: `Saved to ${fullPath} (${buffer.length} bytes)` }],
240
+ };
241
+ }
242
+ catch (error) {
243
+ return handleGmailError(error, account);
244
+ }
245
+ });
246
+ server.registerTool('gmail_create_draft', {
247
+ description: 'Create a Gmail draft without sending',
248
+ inputSchema: {
249
+ account: accountEnum.describe('Google account alias'),
250
+ to: z.string().describe('Recipient(s), comma-separated'),
251
+ subject: z.string().describe('Email subject'),
252
+ body: z.string().describe('Plain text body'),
253
+ cc: z.string().optional().describe('CC recipients, comma-separated'),
254
+ replyToThreadId: z.string().optional()
255
+ .describe('Thread ID to associate the draft with'),
256
+ },
257
+ }, async ({ account, to, subject, body, cc, replyToThreadId }) => {
258
+ try {
259
+ const auth = await getClient(account);
260
+ const gmail = google.gmail({ version: 'v1', auth });
261
+ const config = (await import('../accounts.js')).ACCOUNT_CONFIG[account];
262
+ const headers = [
263
+ `From: ${encodeAddressHeader(config.email)}`,
264
+ `To: ${encodeAddressHeader(to)}`,
265
+ `Subject: ${encodeHeaderValue(subject)}`,
266
+ 'MIME-Version: 1.0',
267
+ 'Content-Type: text/plain; charset="UTF-8"',
268
+ 'Content-Transfer-Encoding: 8bit',
269
+ ];
270
+ if (cc)
271
+ headers.push(`Cc: ${encodeAddressHeader(cc)}`);
272
+ const rawMessage = [...headers, '', normalizeBodyLineEndings(body)].join('\r\n');
273
+ const encoded = Buffer.from(rawMessage, 'utf-8').toString('base64url');
274
+ const draftParams = {
275
+ userId: 'me',
276
+ requestBody: {
277
+ message: { raw: encoded },
278
+ },
279
+ };
280
+ if (replyToThreadId) {
281
+ draftParams.requestBody.message.threadId = replyToThreadId;
282
+ }
283
+ const res = await gmail.users.drafts.create(draftParams);
284
+ return {
285
+ content: [{
286
+ type: 'text',
287
+ text: JSON.stringify({ draftId: res.data.id, threadId: res.data.message?.threadId }, null, 2),
288
+ }],
289
+ };
290
+ }
291
+ catch (error) {
292
+ return handleGmailError(error, account);
293
+ }
294
+ });
295
+ server.registerTool('gmail_modify_labels', {
296
+ description: 'Add or remove labels on a Gmail message. Use system label IDs like STARRED, UNREAD, INBOX, TRASH, or custom label IDs from gmail_list_labels.',
297
+ inputSchema: {
298
+ account: accountEnum.describe('Google account alias'),
299
+ messageId: z.string().describe('Gmail message ID'),
300
+ addLabelIds: z.array(z.string()).optional().describe('Label IDs to add'),
301
+ removeLabelIds: z.array(z.string()).optional().describe('Label IDs to remove'),
302
+ },
303
+ }, async ({ account, messageId, addLabelIds, removeLabelIds }) => {
304
+ try {
305
+ const auth = await getClient(account);
306
+ const gmail = google.gmail({ version: 'v1', auth });
307
+ const res = await gmail.users.messages.modify({
308
+ userId: 'me',
309
+ id: messageId,
310
+ requestBody: {
311
+ addLabelIds: addLabelIds ?? [],
312
+ removeLabelIds: removeLabelIds ?? [],
313
+ },
314
+ });
315
+ return {
316
+ content: [{ type: 'text', text: JSON.stringify(res.data, null, 2) }],
317
+ };
318
+ }
319
+ catch (error) {
320
+ return handleGmailError(error, account);
321
+ }
322
+ });
323
+ server.registerTool('gmail_trash', {
324
+ description: 'Move a Gmail message to Trash (recoverable)',
325
+ inputSchema: {
326
+ account: accountEnum.describe('Google account alias'),
327
+ messageId: z.string().describe('Gmail message ID'),
328
+ },
329
+ }, async ({ account, messageId }) => {
330
+ try {
331
+ const auth = await getClient(account);
332
+ const gmail = google.gmail({ version: 'v1', auth });
333
+ const res = await gmail.users.messages.trash({ userId: 'me', id: messageId });
334
+ return {
335
+ content: [{ type: 'text', text: JSON.stringify(res.data, null, 2) }],
336
+ };
337
+ }
338
+ catch (error) {
339
+ return handleGmailError(error, account);
340
+ }
341
+ });
342
+ server.registerTool('gmail_delete', {
343
+ description: 'Permanently and irreversibly delete a Gmail message. No recovery possible.',
344
+ inputSchema: {
345
+ account: accountEnum.describe('Google account alias'),
346
+ messageId: z.string().describe('Gmail message ID'),
347
+ },
348
+ }, async ({ account, messageId }) => {
349
+ try {
350
+ const auth = await getClient(account);
351
+ const gmail = google.gmail({ version: 'v1', auth });
352
+ await gmail.users.messages.delete({ userId: 'me', id: messageId });
353
+ return {
354
+ content: [{ type: 'text', text: JSON.stringify({ deleted: true, messageId }, null, 2) }],
355
+ };
356
+ }
357
+ catch (error) {
358
+ return handleGmailError(error, account);
359
+ }
360
+ });
361
+ server.registerTool('gmail_batch_modify', {
362
+ description: 'Add/remove labels across up to 1000 Gmail messages at once. Useful for bulk archiving, marking as read, etc.',
363
+ inputSchema: {
364
+ account: accountEnum.describe('Google account alias'),
365
+ messageIds: z.array(z.string()).describe('Message IDs (up to 1000)'),
366
+ addLabelIds: z.array(z.string()).optional().describe('Label IDs to add'),
367
+ removeLabelIds: z.array(z.string()).optional().describe('Label IDs to remove'),
368
+ },
369
+ }, async ({ account, messageIds, addLabelIds, removeLabelIds }) => {
370
+ try {
371
+ const auth = await getClient(account);
372
+ const gmail = google.gmail({ version: 'v1', auth });
373
+ await gmail.users.messages.batchModify({
374
+ userId: 'me',
375
+ requestBody: {
376
+ ids: messageIds,
377
+ addLabelIds: addLabelIds ?? [],
378
+ removeLabelIds: removeLabelIds ?? [],
379
+ },
380
+ });
381
+ return {
382
+ content: [{ type: 'text', text: JSON.stringify({ modified: messageIds.length }, null, 2) }],
383
+ };
384
+ }
385
+ catch (error) {
386
+ return handleGmailError(error, account);
387
+ }
388
+ });
389
+ server.registerTool('gmail_batch_delete', {
390
+ description: 'Permanently delete multiple Gmail messages. Irreversible.',
391
+ inputSchema: {
392
+ account: accountEnum.describe('Google account alias'),
393
+ messageIds: z.array(z.string()).describe('Message IDs (up to 1000)'),
394
+ },
395
+ }, async ({ account, messageIds }) => {
396
+ try {
397
+ const auth = await getClient(account);
398
+ const gmail = google.gmail({ version: 'v1', auth });
399
+ await gmail.users.messages.batchDelete({
400
+ userId: 'me',
401
+ requestBody: { ids: messageIds },
402
+ });
403
+ return {
404
+ content: [{ type: 'text', text: JSON.stringify({ deleted: messageIds.length }, null, 2) }],
405
+ };
406
+ }
407
+ catch (error) {
408
+ return handleGmailError(error, account);
409
+ }
410
+ });
411
+ server.registerTool('gmail_list_drafts', {
412
+ description: 'List all drafts in a Gmail mailbox',
413
+ inputSchema: {
414
+ account: accountEnum.describe('Google account alias'),
415
+ maxResults: z.number().min(1).max(100).default(20).optional()
416
+ .describe('Max results to return (default: 20)'),
417
+ query: z.string().optional().describe('Gmail search syntax to filter drafts'),
418
+ },
419
+ }, async ({ account, maxResults, query }) => {
420
+ try {
421
+ const auth = await getClient(account);
422
+ const gmail = google.gmail({ version: 'v1', auth });
423
+ const res = await gmail.users.drafts.list({
424
+ userId: 'me',
425
+ maxResults: maxResults ?? 20,
426
+ q: query,
427
+ });
428
+ return {
429
+ content: [{ type: 'text', text: JSON.stringify(res.data.drafts ?? [], null, 2) }],
430
+ };
431
+ }
432
+ catch (error) {
433
+ return handleGmailError(error, account);
434
+ }
435
+ });
436
+ server.registerTool('gmail_get_draft', {
437
+ description: 'Read the full content of a specific Gmail draft',
438
+ inputSchema: {
439
+ account: accountEnum.describe('Google account alias'),
440
+ draftId: z.string().describe('Draft ID'),
441
+ },
442
+ }, async ({ account, draftId }) => {
443
+ try {
444
+ const auth = await getClient(account);
445
+ const gmail = google.gmail({ version: 'v1', auth });
446
+ const res = await gmail.users.drafts.get({
447
+ userId: 'me',
448
+ id: draftId,
449
+ format: 'full',
450
+ });
451
+ return {
452
+ content: [{ type: 'text', text: JSON.stringify(res.data, null, 2) }],
453
+ };
454
+ }
455
+ catch (error) {
456
+ return handleGmailError(error, account);
457
+ }
458
+ });
459
+ server.registerTool('gmail_send_draft', {
460
+ description: 'Send an existing Gmail draft by its draft ID',
461
+ inputSchema: {
462
+ account: accountEnum.describe('Google account alias'),
463
+ draftId: z.string().describe('Draft ID to send'),
464
+ },
465
+ }, async ({ account, draftId }) => {
466
+ try {
467
+ const auth = await getClient(account);
468
+ const gmail = google.gmail({ version: 'v1', auth });
469
+ const res = await gmail.users.drafts.send({
470
+ userId: 'me',
471
+ requestBody: { id: draftId },
472
+ });
473
+ return {
474
+ content: [{ type: 'text', text: JSON.stringify(res.data, null, 2) }],
475
+ };
476
+ }
477
+ catch (error) {
478
+ return handleGmailError(error, account);
479
+ }
480
+ });
481
+ server.registerTool('gmail_list_labels', {
482
+ description: 'List all Gmail labels (system and user-defined). Use to get label IDs for gmail_modify_labels.',
483
+ inputSchema: {
484
+ account: accountEnum.describe('Google account alias'),
485
+ },
486
+ }, async ({ account }) => {
487
+ try {
488
+ const auth = await getClient(account);
489
+ const gmail = google.gmail({ version: 'v1', auth });
490
+ const res = await gmail.users.labels.list({ userId: 'me' });
491
+ return {
492
+ content: [{ type: 'text', text: JSON.stringify(res.data.labels ?? [], null, 2) }],
493
+ };
494
+ }
495
+ catch (error) {
496
+ return handleGmailError(error, account);
497
+ }
498
+ });
499
+ server.registerTool('gmail_create_label', {
500
+ description: 'Create a new custom Gmail label',
501
+ inputSchema: {
502
+ account: accountEnum.describe('Google account alias'),
503
+ name: z.string().describe('Label name, e.g. "Ideacrafters/ComptaLegal"'),
504
+ messageListVisibility: z.enum(['show', 'hide']).optional()
505
+ .describe('Whether messages with this label show in message list (default: show)'),
506
+ labelListVisibility: z.enum(['labelShow', 'labelShowIfUnread', 'labelHide']).optional()
507
+ .describe('Whether the label appears in the label list (default: labelShow)'),
508
+ },
509
+ }, async ({ account, name, messageListVisibility, labelListVisibility }) => {
510
+ try {
511
+ const auth = await getClient(account);
512
+ const gmail = google.gmail({ version: 'v1', auth });
513
+ const res = await gmail.users.labels.create({
514
+ userId: 'me',
515
+ requestBody: {
516
+ name,
517
+ messageListVisibility: messageListVisibility ?? 'show',
518
+ labelListVisibility: labelListVisibility ?? 'labelShow',
519
+ },
520
+ });
521
+ return {
522
+ content: [{ type: 'text', text: JSON.stringify(res.data, null, 2) }],
523
+ };
524
+ }
525
+ catch (error) {
526
+ return handleGmailError(error, account);
527
+ }
528
+ });
529
+ server.registerTool('gmail_delete_label', {
530
+ description: 'Permanently delete a Gmail label and remove it from all messages',
531
+ inputSchema: {
532
+ account: accountEnum.describe('Google account alias'),
533
+ labelId: z.string().describe('Label ID to delete'),
534
+ },
535
+ }, async ({ account, labelId }) => {
536
+ try {
537
+ const auth = await getClient(account);
538
+ const gmail = google.gmail({ version: 'v1', auth });
539
+ await gmail.users.labels.delete({ userId: 'me', id: labelId });
540
+ return {
541
+ content: [{ type: 'text', text: JSON.stringify({ deleted: true, labelId }, null, 2) }],
542
+ };
543
+ }
544
+ catch (error) {
545
+ return handleGmailError(error, account);
546
+ }
547
+ });
548
+ server.registerTool('gmail_get_profile', {
549
+ description: 'Get Gmail account profile: email address, total messages, total threads, and current history ID',
550
+ inputSchema: {
551
+ account: accountEnum.describe('Google account alias'),
552
+ },
553
+ }, async ({ account }) => {
554
+ try {
555
+ const auth = await getClient(account);
556
+ const gmail = google.gmail({ version: 'v1', auth });
557
+ const res = await gmail.users.getProfile({ userId: 'me' });
558
+ return {
559
+ content: [{ type: 'text', text: JSON.stringify(res.data, null, 2) }],
560
+ };
561
+ }
562
+ catch (error) {
563
+ return handleGmailError(error, account);
564
+ }
565
+ });
566
+ server.registerTool('gmail_list_history', {
567
+ description: 'Get all mailbox changes since a given historyId. Useful for detecting new emails since last check.',
568
+ inputSchema: {
569
+ account: accountEnum.describe('Google account alias'),
570
+ startHistoryId: z.string().describe('History ID from a previous gmail_get_profile or gmail_read response'),
571
+ maxResults: z.number().min(1).max(500).default(100).optional()
572
+ .describe('Max results to return (default: 100)'),
573
+ historyTypes: z.array(z.enum(['messageAdded', 'messageDeleted', 'labelAdded', 'labelRemoved'])).optional()
574
+ .describe('Filter by history event types'),
575
+ },
576
+ }, async ({ account, startHistoryId, maxResults, historyTypes }) => {
577
+ try {
578
+ const auth = await getClient(account);
579
+ const gmail = google.gmail({ version: 'v1', auth });
580
+ const res = await gmail.users.history.list({
581
+ userId: 'me',
582
+ startHistoryId,
583
+ maxResults: maxResults ?? 100,
584
+ historyTypes: historyTypes,
585
+ });
586
+ return {
587
+ content: [{ type: 'text', text: JSON.stringify(res.data, null, 2) }],
588
+ };
589
+ }
590
+ catch (error) {
591
+ return handleGmailError(error, account);
592
+ }
593
+ });
594
+ server.registerTool('gmail_get_vacation', {
595
+ description: 'Read current Gmail vacation responder settings',
596
+ inputSchema: {
597
+ account: accountEnum.describe('Google account alias'),
598
+ },
599
+ }, async ({ account }) => {
600
+ try {
601
+ const auth = await getClient(account);
602
+ const gmail = google.gmail({ version: 'v1', auth });
603
+ const res = await gmail.users.settings.getVacation({ userId: 'me' });
604
+ return {
605
+ content: [{ type: 'text', text: JSON.stringify(res.data, null, 2) }],
606
+ };
607
+ }
608
+ catch (error) {
609
+ return handleGmailError(error, account);
610
+ }
611
+ });
612
+ server.registerTool('gmail_set_vacation', {
613
+ description: 'Enable or disable Gmail vacation responder with a custom message',
614
+ inputSchema: {
615
+ account: accountEnum.describe('Google account alias'),
616
+ enableAutoReply: z.boolean().describe('Whether to enable the vacation responder'),
617
+ responseSubject: z.string().optional().describe('Subject line for auto-reply'),
618
+ responseBodyPlainText: z.string().optional().describe('Plain text body for auto-reply'),
619
+ startTime: z.string().optional().describe('Start time as Unix timestamp in ms'),
620
+ endTime: z.string().optional().describe('End time as Unix timestamp in ms'),
621
+ restrictToContacts: z.boolean().optional().describe('Only reply to contacts (default: false)'),
622
+ restrictToDomain: z.boolean().optional().describe('Only reply to same domain (default: false)'),
623
+ },
624
+ }, async ({ account, enableAutoReply, responseSubject, responseBodyPlainText, startTime, endTime, restrictToContacts, restrictToDomain }) => {
625
+ try {
626
+ const auth = await getClient(account);
627
+ const gmail = google.gmail({ version: 'v1', auth });
628
+ const res = await gmail.users.settings.updateVacation({
629
+ userId: 'me',
630
+ requestBody: {
631
+ enableAutoReply,
632
+ responseSubject,
633
+ responseBodyPlainText,
634
+ startTime,
635
+ endTime,
636
+ restrictToContacts: restrictToContacts ?? false,
637
+ restrictToDomain: restrictToDomain ?? false,
638
+ },
639
+ });
640
+ return {
641
+ content: [{ type: 'text', text: JSON.stringify(res.data, null, 2) }],
642
+ };
643
+ }
644
+ catch (error) {
645
+ return handleGmailError(error, account);
646
+ }
647
+ });
648
+ }
649
+ function handleGmailError(error, account) {
650
+ return handleGoogleApiError(error, account);
651
+ }
@@ -0,0 +1,2 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerMeetTools(server: McpServer): void;