google-tools-mcp 1.2.12 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +537 -276
  3. package/dist/cachedToolsList.js +52 -52
  4. package/dist/googleDocsApiHelpers.js +76 -12
  5. package/dist/helpers.js +572 -252
  6. package/dist/httpAuth.js +296 -0
  7. package/dist/index.js +162 -11
  8. package/dist/logger.js +87 -87
  9. package/dist/markdown-transformer/docsToMarkdown.js +128 -0
  10. package/dist/markdown-transformer/index.js +17 -7
  11. package/dist/markdown-transformer/markdownToDocs.js +185 -27
  12. package/dist/readTracker.js +136 -17
  13. package/dist/sessionContext.js +37 -0
  14. package/dist/setup.js +187 -9
  15. package/dist/tools/calendar/getBusy.js +64 -64
  16. package/dist/tools/calendar/getEvents.js +141 -141
  17. package/dist/tools/calendar/getFree.js +225 -225
  18. package/dist/tools/calendar/index.js +19 -19
  19. package/dist/tools/calendar/listCalendars.js +38 -38
  20. package/dist/tools/calendar/listRecurringInstances.js +83 -83
  21. package/dist/tools/calendar/manageCalendar.js +121 -121
  22. package/dist/tools/calendar/manageEvent.js +258 -258
  23. package/dist/tools/calendar/moveEvent.js +66 -66
  24. package/dist/tools/docs/addTab.js +10 -12
  25. package/dist/tools/docs/appendToGoogleDoc.js +9 -4
  26. package/dist/tools/docs/deleteRange.js +9 -4
  27. package/dist/tools/docs/findAndReplace.js +9 -4
  28. package/dist/tools/docs/formatting/applyParagraphStyle.js +4 -1
  29. package/dist/tools/docs/formatting/getFormatting.js +171 -171
  30. package/dist/tools/docs/insertImage.js +15 -2
  31. package/dist/tools/docs/insertPageBreak.js +4 -1
  32. package/dist/tools/docs/insertTable.js +4 -1
  33. package/dist/tools/docs/insertTableWithData.js +4 -1
  34. package/dist/tools/docs/modifyText.js +14 -4
  35. package/dist/tools/docs/modifyText.test.js +84 -84
  36. package/dist/tools/docs/readGoogleDoc.js +71 -11
  37. package/dist/tools/docs/renameTab.js +12 -14
  38. package/dist/tools/drafts.js +165 -165
  39. package/dist/tools/drive/createDocument.js +12 -0
  40. package/dist/tools/drive/downloadFile.js +268 -268
  41. package/dist/tools/drive/getFileInfo.js +48 -48
  42. package/dist/tools/drive/getFilePath.js +50 -50
  43. package/dist/tools/drive/listDriveFiles.js +112 -112
  44. package/dist/tools/drive/listSharedDrives.js +52 -52
  45. package/dist/tools/drive/listSharedWithMe.js +97 -97
  46. package/dist/tools/drive/uploadFile.js +111 -111
  47. package/dist/tools/extras/index.js +9 -9
  48. package/dist/tools/extras/readDriveFile.js +197 -196
  49. package/dist/tools/extras/readFile.js +84 -84
  50. package/dist/tools/extras/searchFileContents.js +81 -81
  51. package/dist/tools/forms/batchUpdateForm.js +81 -81
  52. package/dist/tools/forms/createForm.js +54 -54
  53. package/dist/tools/forms/getForm.js +118 -118
  54. package/dist/tools/forms/getFormResponse.js +45 -45
  55. package/dist/tools/forms/index.js +15 -15
  56. package/dist/tools/forms/listFormResponses.js +56 -56
  57. package/dist/tools/forms/setPublishSettings.js +59 -59
  58. package/dist/tools/gmail/drafts.js +165 -165
  59. package/dist/tools/gmail/labels.js +54 -83
  60. package/dist/tools/gmail/messages.js +444 -441
  61. package/dist/tools/gmail/settings.js +358 -528
  62. package/dist/tools/gmail/threads.js +286 -164
  63. package/dist/tools/index.js +511 -473
  64. package/dist/tools/labels.js +103 -103
  65. package/dist/tools/legacyAliases.js +426 -0
  66. package/dist/tools/maps/directions.js +72 -0
  67. package/dist/tools/maps/geocode.js +16 -0
  68. package/dist/tools/maps/index.js +15 -0
  69. package/dist/tools/maps/mapsClient.js +83 -0
  70. package/dist/tools/maps/placeDetails.js +16 -0
  71. package/dist/tools/maps/reverseGeocode.js +19 -0
  72. package/dist/tools/maps/searchNearby.js +54 -0
  73. package/dist/tools/maps/searchPlaces.js +24 -0
  74. package/dist/tools/messages.js +448 -448
  75. package/dist/tools/settings.js +528 -528
  76. package/dist/tools/slides/createPresentation.js +104 -104
  77. package/dist/tools/slides/createShape.js +92 -92
  78. package/dist/tools/slides/createTextBox.js +82 -82
  79. package/dist/tools/slides/deleteSlide.js +30 -30
  80. package/dist/tools/slides/duplicateSlide.js +37 -37
  81. package/dist/tools/slides/exportThumbnail.js +42 -42
  82. package/dist/tools/slides/formatParagraph.js +72 -72
  83. package/dist/tools/slides/formatText.js +84 -84
  84. package/dist/tools/slides/getPresentation.js +87 -87
  85. package/dist/tools/slides/index.js +33 -33
  86. package/dist/tools/slides/reorderSlides.js +41 -41
  87. package/dist/tools/slides/replaceAllText.js +46 -46
  88. package/dist/tools/slides/setBackground.js +58 -58
  89. package/dist/tools/slides/speakerNotes.js +102 -102
  90. package/dist/tools/slides/styleShape.js +111 -111
  91. package/dist/tools/slides/updatePresentation.js +128 -128
  92. package/dist/tools/threads.js +145 -145
  93. package/dist/tools/utils/appendMarkdownToGoogleDoc.js +30 -6
  94. package/dist/tools/utils/replaceDocumentWithMarkdown.js +71 -8
  95. package/dist/updateCheck.js +202 -0
  96. package/dist/workspace.js +122 -0
  97. package/package.json +82 -81
@@ -1,528 +1,358 @@
1
- // Gmail Settings tools (settings, delegates, filters, forwarding, send-as, S/MIME)
2
- import { z } from 'zod';
3
- import { getGmailClient } from '../../clients.js';
4
-
5
- export function register(server) {
6
- // --- Core Settings ---
7
-
8
- server.addTool({
9
- name: 'get_auto_forwarding',
10
- description: 'Gets auto-forwarding settings',
11
- parameters: z.object({}),
12
- execute: async () => {
13
- const gmail = await getGmailClient();
14
- const { data } = await gmail.users.settings.getAutoForwarding({ userId: 'me' });
15
- return JSON.stringify(data);
16
- },
17
- });
18
-
19
- server.addTool({
20
- name: 'update_auto_forwarding',
21
- description: 'Updates automatic forwarding settings',
22
- parameters: z.object({
23
- enabled: z.boolean().describe("Whether all incoming mail is automatically forwarded"),
24
- emailAddress: z.string().describe("Email address to forward to"),
25
- disposition: z.enum(['leaveInInbox', 'archive', 'trash', 'markRead']).describe("What to do with forwarded messages"),
26
- }),
27
- execute: async (params) => {
28
- const gmail = await getGmailClient();
29
- const { data } = await gmail.users.settings.updateAutoForwarding({ userId: 'me', requestBody: params });
30
- return JSON.stringify(data);
31
- },
32
- });
33
-
34
- server.addTool({
35
- name: 'get_imap',
36
- description: 'Gets IMAP settings',
37
- parameters: z.object({}),
38
- execute: async () => {
39
- const gmail = await getGmailClient();
40
- const { data } = await gmail.users.settings.getImap({ userId: 'me' });
41
- return JSON.stringify(data);
42
- },
43
- });
44
-
45
- server.addTool({
46
- name: 'update_imap',
47
- description: 'Updates IMAP settings',
48
- parameters: z.object({
49
- enabled: z.boolean().describe("Whether IMAP is enabled"),
50
- expungeBehavior: z.enum(['archive', 'trash', 'deleteForever']).optional().describe("Action on deleted+expunged messages"),
51
- maxFolderSize: z.number().optional().describe("Max messages accessible through IMAP"),
52
- }),
53
- execute: async (params) => {
54
- const gmail = await getGmailClient();
55
- const { data } = await gmail.users.settings.updateImap({ userId: 'me', requestBody: params });
56
- return JSON.stringify(data);
57
- },
58
- });
59
-
60
- server.addTool({
61
- name: 'get_language',
62
- description: 'Gets language settings',
63
- parameters: z.object({}),
64
- execute: async () => {
65
- const gmail = await getGmailClient();
66
- const { data } = await gmail.users.settings.getLanguage({ userId: 'me' });
67
- return JSON.stringify(data);
68
- },
69
- });
70
-
71
- server.addTool({
72
- name: 'update_language',
73
- description: 'Updates language settings',
74
- parameters: z.object({
75
- displayLanguage: z.string().describe("Language to display Gmail in (RFC 3066 Language Tag)"),
76
- }),
77
- execute: async (params) => {
78
- const gmail = await getGmailClient();
79
- const { data } = await gmail.users.settings.updateLanguage({ userId: 'me', requestBody: params });
80
- return JSON.stringify(data);
81
- },
82
- });
83
-
84
- server.addTool({
85
- name: 'get_pop',
86
- description: 'Gets POP settings',
87
- parameters: z.object({}),
88
- execute: async () => {
89
- const gmail = await getGmailClient();
90
- const { data } = await gmail.users.settings.getPop({ userId: 'me' });
91
- return JSON.stringify(data);
92
- },
93
- });
94
-
95
- server.addTool({
96
- name: 'update_pop',
97
- description: 'Updates POP settings',
98
- parameters: z.object({
99
- accessWindow: z.enum(['disabled', 'allMail', 'fromNowOn']).describe("Range of messages accessible via POP"),
100
- disposition: z.enum(['archive', 'trash', 'leaveInInbox']).describe("Action after POP fetch"),
101
- }),
102
- execute: async (params) => {
103
- const gmail = await getGmailClient();
104
- const { data } = await gmail.users.settings.updatePop({ userId: 'me', requestBody: params });
105
- return JSON.stringify(data);
106
- },
107
- });
108
-
109
- server.addTool({
110
- name: 'get_vacation',
111
- description: 'Get vacation responder settings',
112
- parameters: z.object({}),
113
- execute: async () => {
114
- const gmail = await getGmailClient();
115
- const { data } = await gmail.users.settings.getVacation({ userId: 'me' });
116
- return JSON.stringify(data);
117
- },
118
- });
119
-
120
- server.addTool({
121
- name: 'update_vacation',
122
- description: 'Update vacation responder settings',
123
- parameters: z.object({
124
- enableAutoReply: z.boolean().describe("Whether the vacation responder is enabled"),
125
- responseSubject: z.string().optional().describe("Subject line for auto-reply"),
126
- responseBodyPlainText: z.string().describe("Response body in plain text"),
127
- restrictToContacts: z.boolean().optional().describe("Only send to contacts"),
128
- restrictToDomain: z.boolean().optional().describe("Only send to same domain"),
129
- startTime: z.string().optional().describe("Start time (epoch ms)"),
130
- endTime: z.string().optional().describe("End time (epoch ms)"),
131
- }),
132
- execute: async (params) => {
133
- const gmail = await getGmailClient();
134
- const { data } = await gmail.users.settings.updateVacation({ userId: 'me', requestBody: params });
135
- return JSON.stringify(data);
136
- },
137
- });
138
-
139
- // --- Delegates ---
140
-
141
- server.addTool({
142
- name: 'add_delegate',
143
- description: 'Adds a delegate to the specified account',
144
- parameters: z.object({
145
- delegateEmail: z.string().describe("Email address of delegate to add"),
146
- }),
147
- execute: async (params) => {
148
- const gmail = await getGmailClient();
149
- const { data } = await gmail.users.settings.delegates.create({ userId: 'me', requestBody: { delegateEmail: params.delegateEmail } });
150
- return JSON.stringify(data);
151
- },
152
- });
153
-
154
- server.addTool({
155
- name: 'remove_delegate',
156
- description: 'Removes the specified delegate',
157
- parameters: z.object({
158
- delegateEmail: z.string().describe("Email address of delegate to remove"),
159
- }),
160
- execute: async (params) => {
161
- const gmail = await getGmailClient();
162
- const { data } = await gmail.users.settings.delegates.delete({ userId: 'me', delegateEmail: params.delegateEmail });
163
- return JSON.stringify(data || { success: true });
164
- },
165
- });
166
-
167
- server.addTool({
168
- name: 'get_delegate',
169
- description: 'Gets the specified delegate',
170
- parameters: z.object({
171
- delegateEmail: z.string().describe("The email address of the delegate"),
172
- }),
173
- execute: async (params) => {
174
- const gmail = await getGmailClient();
175
- const { data } = await gmail.users.settings.delegates.get({ userId: 'me', delegateEmail: params.delegateEmail });
176
- return JSON.stringify(data);
177
- },
178
- });
179
-
180
- server.addTool({
181
- name: 'list_delegates',
182
- description: 'Lists the delegates for the specified account',
183
- parameters: z.object({}),
184
- execute: async () => {
185
- const gmail = await getGmailClient();
186
- const { data } = await gmail.users.settings.delegates.list({ userId: 'me' });
187
- return JSON.stringify(data);
188
- },
189
- });
190
-
191
- // --- Filters ---
192
-
193
- server.addTool({
194
- name: 'create_filter',
195
- description: 'Creates a filter',
196
- parameters: z.object({
197
- criteria: z.object({
198
- from: z.string().optional().describe("Sender's display name or email"),
199
- to: z.string().optional().describe("Recipient's display name or email"),
200
- subject: z.string().optional().describe("Case-insensitive phrase in subject"),
201
- query: z.string().optional().describe("Gmail search query for filter criteria"),
202
- negatedQuery: z.string().optional().describe("Query for criteria the message must NOT match"),
203
- hasAttachment: z.boolean().optional().describe("Whether the message has any attachment"),
204
- excludeChats: z.boolean().optional().describe("Exclude chats from results"),
205
- size: z.number().optional().describe("Size of RFC822 message in bytes"),
206
- sizeComparison: z.enum(['smaller', 'larger']).optional().describe("Size comparison operator"),
207
- }).describe("Filter criteria"),
208
- action: z.object({
209
- addLabelIds: z.array(z.string()).optional().describe("Labels to add"),
210
- removeLabelIds: z.array(z.string()).optional().describe("Labels to remove"),
211
- forward: z.string().optional().describe("Email to forward to"),
212
- }).describe("Actions on matching messages"),
213
- }),
214
- execute: async (params) => {
215
- const gmail = await getGmailClient();
216
- const { data } = await gmail.users.settings.filters.create({ userId: 'me', requestBody: params });
217
- return JSON.stringify(data);
218
- },
219
- });
220
-
221
- server.addTool({
222
- name: 'delete_filter',
223
- description: 'Deletes a filter',
224
- parameters: z.object({
225
- id: z.string().describe("The ID of the filter to delete"),
226
- }),
227
- execute: async (params) => {
228
- const gmail = await getGmailClient();
229
- const { data } = await gmail.users.settings.filters.delete({ userId: 'me', id: params.id });
230
- return JSON.stringify(data || { success: true });
231
- },
232
- });
233
-
234
- server.addTool({
235
- name: 'get_filter',
236
- description: 'Gets a filter',
237
- parameters: z.object({
238
- id: z.string().describe("The ID of the filter to retrieve"),
239
- }),
240
- execute: async (params) => {
241
- const gmail = await getGmailClient();
242
- const { data } = await gmail.users.settings.filters.get({ userId: 'me', id: params.id });
243
- return JSON.stringify(data);
244
- },
245
- });
246
-
247
- server.addTool({
248
- name: 'list_filters',
249
- description: 'Lists the message filters of a Gmail user',
250
- parameters: z.object({}),
251
- execute: async () => {
252
- const gmail = await getGmailClient();
253
- const { data } = await gmail.users.settings.filters.list({ userId: 'me' });
254
- return JSON.stringify(data);
255
- },
256
- });
257
-
258
- // --- Forwarding Addresses ---
259
-
260
- server.addTool({
261
- name: 'create_forwarding_address',
262
- description: 'Creates a forwarding address',
263
- parameters: z.object({
264
- forwardingEmail: z.string().describe("An email address to forward messages to"),
265
- }),
266
- execute: async (params) => {
267
- const gmail = await getGmailClient();
268
- const { data } = await gmail.users.settings.forwardingAddresses.create({ userId: 'me', requestBody: params });
269
- return JSON.stringify(data);
270
- },
271
- });
272
-
273
- server.addTool({
274
- name: 'delete_forwarding_address',
275
- description: 'Deletes the specified forwarding address',
276
- parameters: z.object({
277
- forwardingEmail: z.string().describe("The forwarding address to delete"),
278
- }),
279
- execute: async (params) => {
280
- const gmail = await getGmailClient();
281
- const { data } = await gmail.users.settings.forwardingAddresses.delete({ userId: 'me', forwardingEmail: params.forwardingEmail });
282
- return JSON.stringify(data || { success: true });
283
- },
284
- });
285
-
286
- server.addTool({
287
- name: 'get_forwarding_address',
288
- description: 'Gets the specified forwarding address',
289
- parameters: z.object({
290
- forwardingEmail: z.string().describe("The forwarding address to retrieve"),
291
- }),
292
- execute: async (params) => {
293
- const gmail = await getGmailClient();
294
- const { data } = await gmail.users.settings.forwardingAddresses.get({ userId: 'me', forwardingEmail: params.forwardingEmail });
295
- return JSON.stringify(data);
296
- },
297
- });
298
-
299
- server.addTool({
300
- name: 'list_forwarding_addresses',
301
- description: 'Lists the forwarding addresses for the specified account',
302
- parameters: z.object({}),
303
- execute: async () => {
304
- const gmail = await getGmailClient();
305
- const { data } = await gmail.users.settings.forwardingAddresses.list({ userId: 'me' });
306
- return JSON.stringify(data);
307
- },
308
- });
309
-
310
- // --- Send-As Aliases ---
311
-
312
- server.addTool({
313
- name: 'create_send_as',
314
- description: 'Creates a custom send-as alias',
315
- parameters: z.object({
316
- sendAsEmail: z.string().describe("Email address for the 'From:' header"),
317
- displayName: z.string().optional().describe("Name for the 'From:' header"),
318
- replyToAddress: z.string().optional().describe("Email for 'Reply-To:' header"),
319
- signature: z.string().optional().describe("Optional HTML signature"),
320
- isPrimary: z.boolean().optional().describe("Whether this is the primary address"),
321
- treatAsAlias: z.boolean().optional().describe("Whether Gmail treats this as an alias"),
322
- }),
323
- execute: async (params) => {
324
- const gmail = await getGmailClient();
325
- const { data } = await gmail.users.settings.sendAs.create({ userId: 'me', requestBody: params });
326
- return JSON.stringify(data);
327
- },
328
- });
329
-
330
- server.addTool({
331
- name: 'delete_send_as',
332
- description: 'Deletes the specified send-as alias',
333
- parameters: z.object({
334
- sendAsEmail: z.string().describe("The send-as alias to delete"),
335
- }),
336
- execute: async (params) => {
337
- const gmail = await getGmailClient();
338
- const { data } = await gmail.users.settings.sendAs.delete({ userId: 'me', sendAsEmail: params.sendAsEmail });
339
- return JSON.stringify(data || { success: true });
340
- },
341
- });
342
-
343
- server.addTool({
344
- name: 'get_send_as',
345
- description: 'Gets the specified send-as alias',
346
- parameters: z.object({
347
- sendAsEmail: z.string().describe("The send-as alias to retrieve"),
348
- }),
349
- execute: async (params) => {
350
- const gmail = await getGmailClient();
351
- const { data } = await gmail.users.settings.sendAs.get({ userId: 'me', sendAsEmail: params.sendAsEmail });
352
- return JSON.stringify(data);
353
- },
354
- });
355
-
356
- server.addTool({
357
- name: 'list_send_as',
358
- description: 'Lists the send-as aliases for the specified account',
359
- parameters: z.object({}),
360
- execute: async () => {
361
- const gmail = await getGmailClient();
362
- const { data } = await gmail.users.settings.sendAs.list({ userId: 'me' });
363
- return JSON.stringify(data);
364
- },
365
- });
366
-
367
- server.addTool({
368
- name: 'patch_send_as',
369
- description: 'Patches the specified send-as alias',
370
- parameters: z.object({
371
- sendAsEmail: z.string().describe("The send-as alias to update"),
372
- displayName: z.string().optional().describe("Name for the 'From:' header"),
373
- replyToAddress: z.string().optional().describe("Email for 'Reply-To:' header"),
374
- signature: z.string().optional().describe("Optional HTML signature"),
375
- isPrimary: z.boolean().optional().describe("Whether this is the primary address"),
376
- treatAsAlias: z.boolean().optional().describe("Whether Gmail treats this as an alias"),
377
- }),
378
- execute: async (params) => {
379
- const { sendAsEmail, ...patchData } = params;
380
- const gmail = await getGmailClient();
381
- const { data } = await gmail.users.settings.sendAs.patch({ userId: 'me', sendAsEmail, requestBody: patchData });
382
- return JSON.stringify(data);
383
- },
384
- });
385
-
386
- server.addTool({
387
- name: 'update_send_as',
388
- description: 'Updates a send-as alias',
389
- parameters: z.object({
390
- sendAsEmail: z.string().describe("The send-as alias to update"),
391
- displayName: z.string().optional().describe("Name for the 'From:' header"),
392
- replyToAddress: z.string().optional().describe("Email for 'Reply-To:' header"),
393
- signature: z.string().optional().describe("Optional HTML signature"),
394
- isPrimary: z.boolean().optional().describe("Whether this is the primary address"),
395
- treatAsAlias: z.boolean().optional().describe("Whether Gmail treats this as an alias"),
396
- }),
397
- execute: async (params) => {
398
- const { sendAsEmail, ...updateData } = params;
399
- const gmail = await getGmailClient();
400
- const { data } = await gmail.users.settings.sendAs.update({ userId: 'me', sendAsEmail, requestBody: updateData });
401
- return JSON.stringify(data);
402
- },
403
- });
404
-
405
- server.addTool({
406
- name: 'verify_send_as',
407
- description: 'Sends a verification email to the specified send-as alias',
408
- parameters: z.object({
409
- sendAsEmail: z.string().describe("The send-as alias to verify"),
410
- }),
411
- execute: async (params) => {
412
- const gmail = await getGmailClient();
413
- const { data } = await gmail.users.settings.sendAs.verify({ userId: 'me', sendAsEmail: params.sendAsEmail });
414
- return JSON.stringify(data || { success: true });
415
- },
416
- });
417
-
418
- // --- S/MIME ---
419
-
420
- server.addTool({
421
- name: 'delete_smime_info',
422
- description: 'Deletes the specified S/MIME config for a send-as alias',
423
- parameters: z.object({
424
- sendAsEmail: z.string().describe("The email address in the 'From:' header"),
425
- id: z.string().describe("The S/MIME config ID"),
426
- }),
427
- execute: async (params) => {
428
- const gmail = await getGmailClient();
429
- const { data } = await gmail.users.settings.sendAs.smimeInfo.delete({ userId: 'me', sendAsEmail: params.sendAsEmail, id: params.id });
430
- return JSON.stringify(data || { success: true });
431
- },
432
- });
433
-
434
- server.addTool({
435
- name: 'get_smime_info',
436
- description: 'Gets the specified S/MIME config for a send-as alias',
437
- parameters: z.object({
438
- sendAsEmail: z.string().describe("The email address in the 'From:' header"),
439
- id: z.string().describe("The S/MIME config ID"),
440
- }),
441
- execute: async (params) => {
442
- const gmail = await getGmailClient();
443
- const { data } = await gmail.users.settings.sendAs.smimeInfo.get({ userId: 'me', sendAsEmail: params.sendAsEmail, id: params.id });
444
- return JSON.stringify(data);
445
- },
446
- });
447
-
448
- server.addTool({
449
- name: 'insert_smime_info',
450
- description: 'Insert (upload) S/MIME config for a send-as alias',
451
- parameters: z.object({
452
- sendAsEmail: z.string().describe("The email address in the 'From:' header"),
453
- encryptedKeyPassword: z.string().describe("Encrypted key password"),
454
- pkcs12: z.string().describe("PKCS#12 format key pair and certificate chain"),
455
- }),
456
- execute: async (params) => {
457
- const gmail = await getGmailClient();
458
- const { data } = await gmail.users.settings.sendAs.smimeInfo.insert({ userId: 'me', sendAsEmail: params.sendAsEmail, requestBody: params });
459
- return JSON.stringify(data);
460
- },
461
- });
462
-
463
- server.addTool({
464
- name: 'list_smime_info',
465
- description: 'Lists S/MIME configs for a send-as alias',
466
- parameters: z.object({
467
- sendAsEmail: z.string().describe("The email address in the 'From:' header"),
468
- }),
469
- execute: async (params) => {
470
- const gmail = await getGmailClient();
471
- const { data } = await gmail.users.settings.sendAs.smimeInfo.list({ userId: 'me', sendAsEmail: params.sendAsEmail });
472
- return JSON.stringify(data);
473
- },
474
- });
475
-
476
- server.addTool({
477
- name: 'set_default_smime_info',
478
- description: 'Sets the default S/MIME config for a send-as alias',
479
- parameters: z.object({
480
- sendAsEmail: z.string().describe("The email address in the 'From:' header"),
481
- id: z.string().describe("The S/MIME config ID"),
482
- }),
483
- execute: async (params) => {
484
- const gmail = await getGmailClient();
485
- const { data } = await gmail.users.settings.sendAs.smimeInfo.setDefault({ userId: 'me', sendAsEmail: params.sendAsEmail, id: params.id });
486
- return JSON.stringify(data || { success: true });
487
- },
488
- });
489
-
490
- // --- Profile & Watch ---
491
-
492
- server.addTool({
493
- name: 'get_profile',
494
- description: 'Get the current user\'s Gmail profile',
495
- parameters: z.object({}),
496
- execute: async () => {
497
- const gmail = await getGmailClient();
498
- const { data } = await gmail.users.getProfile({ userId: 'me' });
499
- return JSON.stringify(data);
500
- },
501
- });
502
-
503
- server.addTool({
504
- name: 'watch_mailbox',
505
- description: 'Watch for changes to the user\'s mailbox via Cloud Pub/Sub',
506
- parameters: z.object({
507
- topicName: z.string().describe("Cloud Pub/Sub topic to publish notifications to"),
508
- labelIds: z.array(z.string()).optional().describe("Label IDs to restrict notifications to"),
509
- labelFilterAction: z.enum(['include', 'exclude']).optional().describe("Whether to include or exclude specified labels"),
510
- }),
511
- execute: async (params) => {
512
- const gmail = await getGmailClient();
513
- const { data } = await gmail.users.watch({ userId: 'me', requestBody: params });
514
- return JSON.stringify(data);
515
- },
516
- });
517
-
518
- server.addTool({
519
- name: 'stop_mail_watch',
520
- description: 'Stop receiving push notifications for the user\'s mailbox',
521
- parameters: z.object({}),
522
- execute: async () => {
523
- const gmail = await getGmailClient();
524
- const { data } = await gmail.users.stop({ userId: 'me' });
525
- return JSON.stringify(data || { success: true });
526
- },
527
- });
528
- }
1
+ // Gmail Settings tools consolidated dispatch tools (issue #31/#32/#33).
2
+ // manageGmailSettings/manageSmime/manageFilter replace the former ~34 granular
3
+ // account-config tools. Profile/watch stay granular (renamed to camelCase).
4
+ import { z } from 'zod';
5
+ import { UserError } from 'fastmcp';
6
+ import { getGmailClient } from '../../clients.js';
7
+
8
+ // ---------------------------------------------------------------------------
9
+ // manageGmailSettings — resource/action dispatch over the Gmail settings API.
10
+ // Each handler reproduces the exact underlying gmail.users.settings.* call the
11
+ // former granular tool made, so this is a surface reshape, not a behavior change.
12
+ // ---------------------------------------------------------------------------
13
+ const SETTINGS_OPS = {
14
+ imap: {
15
+ get: (gmail) => gmail.users.settings.getImap({ userId: 'me' }),
16
+ update: (gmail, payload) => gmail.users.settings.updateImap({ userId: 'me', requestBody: payload }),
17
+ },
18
+ pop: {
19
+ get: (gmail) => gmail.users.settings.getPop({ userId: 'me' }),
20
+ update: (gmail, payload) => gmail.users.settings.updatePop({ userId: 'me', requestBody: payload }),
21
+ },
22
+ vacation: {
23
+ get: (gmail) => gmail.users.settings.getVacation({ userId: 'me' }),
24
+ update: (gmail, payload) => gmail.users.settings.updateVacation({ userId: 'me', requestBody: payload }),
25
+ },
26
+ language: {
27
+ get: (gmail) => gmail.users.settings.getLanguage({ userId: 'me' }),
28
+ update: (gmail, payload) => gmail.users.settings.updateLanguage({ userId: 'me', requestBody: payload }),
29
+ },
30
+ autoForwarding: {
31
+ get: (gmail) => gmail.users.settings.getAutoForwarding({ userId: 'me' }),
32
+ update: (gmail, payload) => gmail.users.settings.updateAutoForwarding({ userId: 'me', requestBody: payload }),
33
+ },
34
+ forwardingAddress: {
35
+ get: (gmail, payload) => gmail.users.settings.forwardingAddresses.get({ userId: 'me', forwardingEmail: payload.forwardingEmail }),
36
+ list: (gmail) => gmail.users.settings.forwardingAddresses.list({ userId: 'me' }),
37
+ create: (gmail, payload) => gmail.users.settings.forwardingAddresses.create({ userId: 'me', requestBody: payload }),
38
+ delete: (gmail, payload) => gmail.users.settings.forwardingAddresses.delete({ userId: 'me', forwardingEmail: payload.forwardingEmail }),
39
+ },
40
+ delegate: {
41
+ get: (gmail, payload) => gmail.users.settings.delegates.get({ userId: 'me', delegateEmail: payload.delegateEmail }),
42
+ list: (gmail) => gmail.users.settings.delegates.list({ userId: 'me' }),
43
+ create: (gmail, payload) => gmail.users.settings.delegates.create({ userId: 'me', requestBody: { delegateEmail: payload.delegateEmail } }),
44
+ delete: (gmail, payload) => gmail.users.settings.delegates.delete({ userId: 'me', delegateEmail: payload.delegateEmail }),
45
+ },
46
+ sendAs: {
47
+ get: (gmail, payload) => gmail.users.settings.sendAs.get({ userId: 'me', sendAsEmail: payload.sendAsEmail }),
48
+ list: (gmail) => gmail.users.settings.sendAs.list({ userId: 'me' }),
49
+ create: (gmail, payload) => gmail.users.settings.sendAs.create({ userId: 'me', requestBody: payload }),
50
+ patch: (gmail, payload) => {
51
+ const { sendAsEmail, ...body } = payload;
52
+ return gmail.users.settings.sendAs.patch({ userId: 'me', sendAsEmail, requestBody: body });
53
+ },
54
+ update: (gmail, payload) => {
55
+ const { sendAsEmail, ...body } = payload;
56
+ return gmail.users.settings.sendAs.update({ userId: 'me', sendAsEmail, requestBody: body });
57
+ },
58
+ delete: (gmail, payload) => gmail.users.settings.sendAs.delete({ userId: 'me', sendAsEmail: payload.sendAsEmail }),
59
+ verify: (gmail, payload) => gmail.users.settings.sendAs.verify({ userId: 'me', sendAsEmail: payload.sendAsEmail }),
60
+ },
61
+ };
62
+
63
+ function validCombosText() {
64
+ return Object.entries(SETTINGS_OPS)
65
+ .map(([resource, actions]) => ` - ${resource}: ${Object.keys(actions).join(', ')}`)
66
+ .join('\n');
67
+ }
68
+
69
+ // Per-operation Zod payload schemas — mirror the exact fields (names, types,
70
+ // enums, and required-ness) of the former granular tools' request schemas, so
71
+ // a malformed payload (wrong type, invalid enum, missing required field) is
72
+ // rejected before it ever reaches the Gmail API instead of only being checked
73
+ // for key *presence*. Actions with no entry here (every "get"/"list" that
74
+ // takes no identifier) take no payload.
75
+ const sendAsProfileFields = {
76
+ displayName: z.string().optional().describe("Name for the 'From:' header"),
77
+ replyToAddress: z.string().optional().describe("Email for 'Reply-To:' header"),
78
+ signature: z.string().optional().describe('Optional HTML signature'),
79
+ isPrimary: z.boolean().optional().describe('Whether this is the primary address'),
80
+ treatAsAlias: z.boolean().optional().describe('Whether Gmail treats this as an alias'),
81
+ };
82
+ const PAYLOAD_SCHEMAS = {
83
+ imap: {
84
+ update: z.object({
85
+ enabled: z.boolean(),
86
+ expungeBehavior: z.enum(['archive', 'trash', 'deleteForever']).optional(),
87
+ maxFolderSize: z.number().optional(),
88
+ }),
89
+ },
90
+ pop: {
91
+ update: z.object({
92
+ accessWindow: z.enum(['disabled', 'allMail', 'fromNowOn']),
93
+ disposition: z.enum(['archive', 'trash', 'leaveInInbox']),
94
+ }),
95
+ },
96
+ vacation: {
97
+ update: z.object({
98
+ enableAutoReply: z.boolean(),
99
+ responseSubject: z.string().optional(),
100
+ responseBodyPlainText: z.string(),
101
+ restrictToContacts: z.boolean().optional(),
102
+ restrictToDomain: z.boolean().optional(),
103
+ startTime: z.string().optional(),
104
+ endTime: z.string().optional(),
105
+ }),
106
+ },
107
+ language: {
108
+ update: z.object({ displayLanguage: z.string() }),
109
+ },
110
+ autoForwarding: {
111
+ update: z.object({
112
+ enabled: z.boolean(),
113
+ emailAddress: z.string(),
114
+ disposition: z.enum(['leaveInInbox', 'archive', 'trash', 'markRead']),
115
+ }),
116
+ },
117
+ forwardingAddress: {
118
+ get: z.object({ forwardingEmail: z.string() }),
119
+ create: z.object({ forwardingEmail: z.string() }),
120
+ delete: z.object({ forwardingEmail: z.string() }),
121
+ },
122
+ delegate: {
123
+ get: z.object({ delegateEmail: z.string() }),
124
+ create: z.object({ delegateEmail: z.string() }),
125
+ delete: z.object({ delegateEmail: z.string() }),
126
+ },
127
+ sendAs: {
128
+ get: z.object({ sendAsEmail: z.string() }),
129
+ delete: z.object({ sendAsEmail: z.string() }),
130
+ verify: z.object({ sendAsEmail: z.string() }),
131
+ create: z.object({ sendAsEmail: z.string(), ...sendAsProfileFields }),
132
+ patch: z.object({ sendAsEmail: z.string(), ...sendAsProfileFields }),
133
+ update: z.object({ sendAsEmail: z.string(), ...sendAsProfileFields }),
134
+ },
135
+ };
136
+
137
+ function formatZodIssues(issues) {
138
+ return issues.map((issue) => `${issue.path.join('.') || '(root)'}: ${issue.message}`).join('; ');
139
+ }
140
+
141
+ // Validates+parses payload against the per-resource/action schema above.
142
+ // Returns the typed payload on success; throws a UserError naming every
143
+ // failing field on failure. Actions with no schema entry take no payload.
144
+ function parsePayload(resource, action, payload) {
145
+ const schema = PAYLOAD_SCHEMAS[resource]?.[action];
146
+ if (!schema) return payload ?? {};
147
+ const result = schema.safeParse(payload ?? {});
148
+ if (!result.success) {
149
+ throw new UserError(
150
+ `resource="${resource}" action="${action}" payload validation failed: ${formatZodIssues(result.error.issues)}`
151
+ );
152
+ }
153
+ return result.data;
154
+ }
155
+
156
+ // Mirrors parsePayload's checks as Zod issues (rather than a thrown UserError)
157
+ // so they surface directly from parameters.parse()/safeParse(), not only from
158
+ // execute(). This is what restores the enum/type guidance a caller building
159
+ // the raw MCP request sees, on top of the runtime check in execute() below.
160
+ function addPayloadIssues(resource, action, payload, ctx) {
161
+ const resourceOps = SETTINGS_OPS[resource];
162
+ if (!resourceOps || !resourceOps[action]) {
163
+ ctx.addIssue({
164
+ code: z.ZodIssueCode.custom,
165
+ message: `Invalid resource/action combination: resource="${resource}" action="${action}".\nValid combinations:\n${validCombosText()}`,
166
+ });
167
+ return;
168
+ }
169
+ const schema = PAYLOAD_SCHEMAS[resource]?.[action];
170
+ if (!schema) return;
171
+ const result = schema.safeParse(payload ?? {});
172
+ if (!result.success) {
173
+ for (const issue of result.error.issues) {
174
+ ctx.addIssue({ ...issue, path: ['payload', ...issue.path] });
175
+ }
176
+ }
177
+ }
178
+
179
+ export function register(server) {
180
+ server.addTool({
181
+ name: 'manageGmailSettings',
182
+ description:
183
+ 'Manage Gmail account settings via a resource/action dispatch. ' +
184
+ 'Provide `resource`, `action`, and (where the action needs a body or identifier) a `payload`.\n\n' +
185
+ 'Valid resource actions:\n' +
186
+ ' - imap: get, update (payload: { enabled, expungeBehavior?, maxFolderSize? })\n' +
187
+ ' - pop: get, update (payload: { accessWindow, disposition })\n' +
188
+ ' - vacation: get, update (payload: { enableAutoReply, responseSubject?, responseBodyPlainText, restrictToContacts?, restrictToDomain?, startTime?, endTime? })\n' +
189
+ ' - language: get, update (payload: { displayLanguage })\n' +
190
+ ' - autoForwarding: get, update (payload: { enabled, emailAddress, disposition })\n' +
191
+ ' - forwardingAddress: list, get/delete (payload: { forwardingEmail }), create (payload: { forwardingEmail })\n' +
192
+ ' - delegate: list, get/create/delete (payload: { delegateEmail })\n' +
193
+ ' - sendAs: list, get/delete/verify (payload: { sendAsEmail }), create/patch/update (payload: { sendAsEmail, displayName?, replyToAddress?, signature?, isPrimary?, treatAsAlias? })',
194
+ parameters: z.object({
195
+ resource: z.enum(['imap', 'pop', 'vacation', 'language', 'autoForwarding', 'forwardingAddress', 'delegate', 'sendAs'])
196
+ .describe('The settings resource to operate on'),
197
+ action: z.enum(['get', 'update', 'list', 'create', 'delete', 'patch', 'verify'])
198
+ .describe('The operation to perform (must be valid for the chosen resource)'),
199
+ payload: z.record(z.string(), z.unknown()).optional()
200
+ .describe('Resource/action-specific fields (request body or identifier). See the description for valid keys per resource.'),
201
+ }).superRefine((data, ctx) => addPayloadIssues(data.resource, data.action, data.payload, ctx)),
202
+ execute: async ({ resource, action, payload = {} }) => {
203
+ const resourceOps = SETTINGS_OPS[resource];
204
+ const handler = resourceOps && resourceOps[action];
205
+ if (!handler) {
206
+ throw new UserError(
207
+ `Invalid resource/action combination: resource="${resource}", action="${action}".\n` +
208
+ `Valid combinations:\n${validCombosText()}`
209
+ );
210
+ }
211
+ const parsedPayload = parsePayload(resource, action, payload);
212
+ const gmail = await getGmailClient();
213
+ const { data } = await handler(gmail, parsedPayload);
214
+ return JSON.stringify(data || { success: true });
215
+ },
216
+ });
217
+
218
+ // --- S/MIME ---
219
+ server.addTool({
220
+ name: 'manageSmime',
221
+ description:
222
+ 'Manage S/MIME configurations for a send-as alias. action: list | get | insert | delete | setDefault. ' +
223
+ 'All actions require sendAsEmail. get/delete/setDefault require id. insert requires encryptedKeyPassword and pkcs12.',
224
+ parameters: z.object({
225
+ action: z.enum(['list', 'get', 'insert', 'delete', 'setDefault']).describe('The S/MIME operation to perform'),
226
+ sendAsEmail: z.string().describe("The email address in the 'From:' header"),
227
+ id: z.string().optional().describe('The S/MIME config ID (required for get, delete, setDefault)'),
228
+ encryptedKeyPassword: z.string().optional().describe('Encrypted key password (required for insert)'),
229
+ pkcs12: z.string().optional().describe('PKCS#12 format key pair and certificate chain (required for insert)'),
230
+ }),
231
+ execute: async (params) => {
232
+ if (['get', 'delete', 'setDefault'].includes(params.action) && !params.id) {
233
+ throw new UserError(`manageSmime action="${params.action}" requires id.`);
234
+ }
235
+ if (params.action === 'insert' && (!params.encryptedKeyPassword || !params.pkcs12)) {
236
+ throw new UserError('manageSmime action="insert" requires encryptedKeyPassword and pkcs12.');
237
+ }
238
+ const gmail = await getGmailClient();
239
+ const smime = gmail.users.settings.sendAs.smimeInfo;
240
+ const { action, sendAsEmail, id } = params;
241
+ let data;
242
+ switch (action) {
243
+ case 'list':
244
+ ({ data } = await smime.list({ userId: 'me', sendAsEmail }));
245
+ break;
246
+ case 'get':
247
+ ({ data } = await smime.get({ userId: 'me', sendAsEmail, id }));
248
+ break;
249
+ case 'insert':
250
+ ({ data } = await smime.insert({
251
+ userId: 'me',
252
+ sendAsEmail,
253
+ requestBody: { sendAsEmail, encryptedKeyPassword: params.encryptedKeyPassword, pkcs12: params.pkcs12 },
254
+ }));
255
+ break;
256
+ case 'delete':
257
+ ({ data } = await smime.delete({ userId: 'me', sendAsEmail, id }));
258
+ break;
259
+ case 'setDefault':
260
+ ({ data } = await smime.setDefault({ userId: 'me', sendAsEmail, id }));
261
+ break;
262
+ }
263
+ return JSON.stringify(data || { success: true });
264
+ },
265
+ });
266
+
267
+ // --- Filters ---
268
+ server.addTool({
269
+ name: 'manageFilter',
270
+ description:
271
+ 'Manage Gmail message filters. action: create | delete | get | list. ' +
272
+ 'create requires criteria and filterAction. get/delete require id.',
273
+ parameters: z.object({
274
+ action: z.enum(['create', 'delete', 'get', 'list']).describe('The filter operation to perform'),
275
+ id: z.string().optional().describe('The filter ID (required for get and delete)'),
276
+ criteria: z.object({
277
+ from: z.string().optional().describe("Sender's display name or email"),
278
+ to: z.string().optional().describe("Recipient's display name or email"),
279
+ subject: z.string().optional().describe('Case-insensitive phrase in subject'),
280
+ query: z.string().optional().describe('Gmail search query for filter criteria'),
281
+ negatedQuery: z.string().optional().describe('Query for criteria the message must NOT match'),
282
+ hasAttachment: z.boolean().optional().describe('Whether the message has any attachment'),
283
+ excludeChats: z.boolean().optional().describe('Exclude chats from results'),
284
+ size: z.number().optional().describe('Size of RFC822 message in bytes'),
285
+ sizeComparison: z.enum(['smaller', 'larger']).optional().describe('Size comparison operator'),
286
+ }).optional().describe('Filter criteria (required for create)'),
287
+ filterAction: z.object({
288
+ addLabelIds: z.array(z.string()).optional().describe('Labels to add'),
289
+ removeLabelIds: z.array(z.string()).optional().describe('Labels to remove'),
290
+ forward: z.string().optional().describe('Email to forward to'),
291
+ }).optional().describe('Actions on matching messages (required for create)'),
292
+ }),
293
+ execute: async (params) => {
294
+ if (params.action === 'create' && (!params.criteria || !params.filterAction)) {
295
+ throw new UserError('manageFilter action="create" requires criteria and filterAction.');
296
+ }
297
+ if (['get', 'delete'].includes(params.action) && !params.id) {
298
+ throw new UserError(`manageFilter action="${params.action}" requires id.`);
299
+ }
300
+ const gmail = await getGmailClient();
301
+ const filters = gmail.users.settings.filters;
302
+ let data;
303
+ switch (params.action) {
304
+ case 'create':
305
+ ({ data } = await filters.create({ userId: 'me', requestBody: { criteria: params.criteria, action: params.filterAction } }));
306
+ break;
307
+ case 'delete':
308
+ ({ data } = await filters.delete({ userId: 'me', id: params.id }));
309
+ break;
310
+ case 'get':
311
+ ({ data } = await filters.get({ userId: 'me', id: params.id }));
312
+ break;
313
+ case 'list':
314
+ ({ data } = await filters.list({ userId: 'me' }));
315
+ break;
316
+ }
317
+ return JSON.stringify(data || { success: true });
318
+ },
319
+ });
320
+
321
+ // --- Profile & Watch (kept granular, renamed to camelCase) ---
322
+ server.addTool({
323
+ name: 'getProfile',
324
+ description: 'Get the current user\'s Gmail profile',
325
+ parameters: z.object({}),
326
+ execute: async () => {
327
+ const gmail = await getGmailClient();
328
+ const { data } = await gmail.users.getProfile({ userId: 'me' });
329
+ return JSON.stringify(data);
330
+ },
331
+ });
332
+
333
+ server.addTool({
334
+ name: 'watchMailbox',
335
+ description: 'Watch for changes to the user\'s mailbox via Cloud Pub/Sub',
336
+ parameters: z.object({
337
+ topicName: z.string().describe("Cloud Pub/Sub topic to publish notifications to"),
338
+ labelIds: z.array(z.string()).optional().describe("Label IDs to restrict notifications to"),
339
+ labelFilterAction: z.enum(['include', 'exclude']).optional().describe("Whether to include or exclude specified labels"),
340
+ }),
341
+ execute: async (params) => {
342
+ const gmail = await getGmailClient();
343
+ const { data } = await gmail.users.watch({ userId: 'me', requestBody: params });
344
+ return JSON.stringify(data);
345
+ },
346
+ });
347
+
348
+ server.addTool({
349
+ name: 'stopMailWatch',
350
+ description: 'Stop receiving push notifications for the user\'s mailbox',
351
+ parameters: z.object({}),
352
+ execute: async () => {
353
+ const gmail = await getGmailClient();
354
+ const { data } = await gmail.users.stop({ userId: 'me' });
355
+ return JSON.stringify(data || { success: true });
356
+ },
357
+ });
358
+ }