google-tools-mcp 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,145 +1,145 @@
1
- // Gmail Thread tools
2
- import { z } from 'zod';
3
- import { getGmailClient } from '../../clients.js';
4
- import { processMessagePart } from '../../helpers.js';
5
-
6
- export function register(server) {
7
- server.addTool({
8
- name: 'get_thread',
9
- description: 'Get a specific thread by ID',
10
- parameters: z.object({
11
- id: z.string().describe("The ID of the thread to retrieve"),
12
- includeBodyHtml: z.boolean().optional().describe("Whether to include the parsed HTML in the return for each body"),
13
- }),
14
- execute: async (params) => {
15
- const gmail = await getGmailClient();
16
- const { data } = await gmail.users.threads.get({ userId: 'me', id: params.id, format: 'full' });
17
- if (data.messages) {
18
- data.messages = data.messages.map(message => {
19
- if (message.payload) {
20
- message.payload = processMessagePart(message.payload, params.includeBodyHtml);
21
- }
22
- return message;
23
- });
24
- }
25
- return JSON.stringify(data);
26
- },
27
- });
28
-
29
- server.addTool({
30
- name: 'list_threads',
31
- description: 'List threads in the user\'s mailbox',
32
- parameters: z.object({
33
- maxResults: z.number().optional().describe("Maximum number of threads to return"),
34
- pageToken: z.string().optional().describe("Page token to retrieve a specific page of results"),
35
- q: z.string().optional().describe("Only return threads matching the specified query"),
36
- labelIds: z.array(z.string()).optional().describe("Only return threads with labels that match all specified label IDs"),
37
- includeSpamTrash: z.boolean().optional().describe("Include threads from SPAM and TRASH"),
38
- includeBodyHtml: z.boolean().optional().describe("Whether to include the parsed HTML in the return for each body"),
39
- }),
40
- execute: async (params) => {
41
- const gmail = await getGmailClient();
42
- const { data } = await gmail.users.threads.list({ userId: 'me', ...params });
43
- if (data.threads) {
44
- data.threads = data.threads.map(thread => {
45
- if (thread.messages) {
46
- thread.messages = thread.messages.map(message => {
47
- if (message.payload) {
48
- message.payload = processMessagePart(message.payload, params.includeBodyHtml);
49
- }
50
- return message;
51
- });
52
- }
53
- return thread;
54
- });
55
- }
56
- return JSON.stringify(data);
57
- },
58
- });
59
-
60
- server.addTool({
61
- name: 'batch_get_threads',
62
- description: 'Get multiple threads by ID in parallel. More efficient than calling get_thread multiple times.',
63
- parameters: z.object({
64
- ids: z.array(z.string()).describe("The IDs of the threads to retrieve"),
65
- includeBodyHtml: z.boolean().optional().describe("Whether to include the parsed HTML in the return for each body"),
66
- }),
67
- execute: async (params) => {
68
- const gmail = await getGmailClient();
69
- const results = await Promise.all(
70
- params.ids.map(async (id) => {
71
- try {
72
- const { data } = await gmail.users.threads.get({ userId: 'me', id, format: 'full' });
73
- if (data.messages) {
74
- data.messages = data.messages.map(message => {
75
- if (message.payload) {
76
- message.payload = processMessagePart(message.payload, params.includeBodyHtml);
77
- }
78
- return message;
79
- });
80
- }
81
- return data;
82
- } catch (error) {
83
- return { id, error: error.message || 'Failed to retrieve thread' };
84
- }
85
- })
86
- );
87
- return JSON.stringify(results);
88
- },
89
- });
90
-
91
- server.addTool({
92
- name: 'modify_thread',
93
- description: 'Modify the labels applied to a thread',
94
- parameters: z.object({
95
- id: z.string().describe("The ID of the thread to modify"),
96
- addLabelIds: z.array(z.string()).optional().describe("Label IDs to add"),
97
- removeLabelIds: z.array(z.string()).optional().describe("Label IDs to remove"),
98
- }),
99
- execute: async (params) => {
100
- const { id, ...threadData } = params;
101
- const gmail = await getGmailClient();
102
- const { data } = await gmail.users.threads.modify({ userId: 'me', id, requestBody: threadData });
103
- return JSON.stringify(data);
104
- },
105
- });
106
-
107
- server.addTool({
108
- name: 'delete_thread',
109
- description: 'Delete a thread',
110
- parameters: z.object({
111
- id: z.string().describe("The ID of the thread to delete"),
112
- }),
113
- execute: async (params) => {
114
- const gmail = await getGmailClient();
115
- const { data } = await gmail.users.threads.delete({ userId: 'me', id: params.id });
116
- return JSON.stringify(data || { success: true });
117
- },
118
- });
119
-
120
- server.addTool({
121
- name: 'trash_thread',
122
- description: 'Move a thread to the trash',
123
- parameters: z.object({
124
- id: z.string().describe("The ID of the thread to move to trash"),
125
- }),
126
- execute: async (params) => {
127
- const gmail = await getGmailClient();
128
- const { data } = await gmail.users.threads.trash({ userId: 'me', id: params.id });
129
- return JSON.stringify(data);
130
- },
131
- });
132
-
133
- server.addTool({
134
- name: 'untrash_thread',
135
- description: 'Remove a thread from the trash',
136
- parameters: z.object({
137
- id: z.string().describe("The ID of the thread to remove from trash"),
138
- }),
139
- execute: async (params) => {
140
- const gmail = await getGmailClient();
141
- const { data } = await gmail.users.threads.untrash({ userId: 'me', id: params.id });
142
- return JSON.stringify(data);
143
- },
144
- });
145
- }
1
+ // Gmail Thread tools
2
+ import { z } from 'zod';
3
+ import { getGmailClient } from '../../clients.js';
4
+ import { processMessagePart } from '../../helpers.js';
5
+
6
+ export function register(server) {
7
+ server.addTool({
8
+ name: 'get_thread',
9
+ description: 'Get a specific thread by ID',
10
+ parameters: z.object({
11
+ id: z.string().describe("The ID of the thread to retrieve"),
12
+ includeBodyHtml: z.boolean().optional().describe("Whether to include the parsed HTML in the return for each body"),
13
+ }),
14
+ execute: async (params) => {
15
+ const gmail = await getGmailClient();
16
+ const { data } = await gmail.users.threads.get({ userId: 'me', id: params.id, format: 'full' });
17
+ if (data.messages) {
18
+ data.messages = data.messages.map(message => {
19
+ if (message.payload) {
20
+ message.payload = processMessagePart(message.payload, params.includeBodyHtml);
21
+ }
22
+ return message;
23
+ });
24
+ }
25
+ return JSON.stringify(data);
26
+ },
27
+ });
28
+
29
+ server.addTool({
30
+ name: 'list_threads',
31
+ description: 'List threads in the user\'s mailbox',
32
+ parameters: z.object({
33
+ maxResults: z.number().optional().describe("Maximum number of threads to return"),
34
+ pageToken: z.string().optional().describe("Page token to retrieve a specific page of results"),
35
+ q: z.string().optional().describe("Only return threads matching the specified query"),
36
+ labelIds: z.array(z.string()).optional().describe("Only return threads with labels that match all specified label IDs"),
37
+ includeSpamTrash: z.boolean().optional().describe("Include threads from SPAM and TRASH"),
38
+ includeBodyHtml: z.boolean().optional().describe("Whether to include the parsed HTML in the return for each body"),
39
+ }),
40
+ execute: async (params) => {
41
+ const gmail = await getGmailClient();
42
+ const { data } = await gmail.users.threads.list({ userId: 'me', ...params });
43
+ if (data.threads) {
44
+ data.threads = data.threads.map(thread => {
45
+ if (thread.messages) {
46
+ thread.messages = thread.messages.map(message => {
47
+ if (message.payload) {
48
+ message.payload = processMessagePart(message.payload, params.includeBodyHtml);
49
+ }
50
+ return message;
51
+ });
52
+ }
53
+ return thread;
54
+ });
55
+ }
56
+ return JSON.stringify(data);
57
+ },
58
+ });
59
+
60
+ server.addTool({
61
+ name: 'batch_get_threads',
62
+ description: 'Get multiple threads by ID in parallel. More efficient than calling get_thread multiple times.',
63
+ parameters: z.object({
64
+ ids: z.array(z.string()).describe("The IDs of the threads to retrieve"),
65
+ includeBodyHtml: z.boolean().optional().describe("Whether to include the parsed HTML in the return for each body"),
66
+ }),
67
+ execute: async (params) => {
68
+ const gmail = await getGmailClient();
69
+ const results = await Promise.all(
70
+ params.ids.map(async (id) => {
71
+ try {
72
+ const { data } = await gmail.users.threads.get({ userId: 'me', id, format: 'full' });
73
+ if (data.messages) {
74
+ data.messages = data.messages.map(message => {
75
+ if (message.payload) {
76
+ message.payload = processMessagePart(message.payload, params.includeBodyHtml);
77
+ }
78
+ return message;
79
+ });
80
+ }
81
+ return data;
82
+ } catch (error) {
83
+ return { id, error: error.message || 'Failed to retrieve thread' };
84
+ }
85
+ })
86
+ );
87
+ return JSON.stringify(results);
88
+ },
89
+ });
90
+
91
+ server.addTool({
92
+ name: 'modify_thread',
93
+ description: 'Modify the labels applied to a thread',
94
+ parameters: z.object({
95
+ id: z.string().describe("The ID of the thread to modify"),
96
+ addLabelIds: z.array(z.string()).optional().describe("Label IDs to add"),
97
+ removeLabelIds: z.array(z.string()).optional().describe("Label IDs to remove"),
98
+ }),
99
+ execute: async (params) => {
100
+ const { id, ...threadData } = params;
101
+ const gmail = await getGmailClient();
102
+ const { data } = await gmail.users.threads.modify({ userId: 'me', id, requestBody: threadData });
103
+ return JSON.stringify(data);
104
+ },
105
+ });
106
+
107
+ server.addTool({
108
+ name: 'delete_thread',
109
+ description: 'Delete a thread',
110
+ parameters: z.object({
111
+ id: z.string().describe("The ID of the thread to delete"),
112
+ }),
113
+ execute: async (params) => {
114
+ const gmail = await getGmailClient();
115
+ const { data } = await gmail.users.threads.delete({ userId: 'me', id: params.id });
116
+ return JSON.stringify(data || { success: true });
117
+ },
118
+ });
119
+
120
+ server.addTool({
121
+ name: 'trash_thread',
122
+ description: 'Move a thread to the trash',
123
+ parameters: z.object({
124
+ id: z.string().describe("The ID of the thread to move to trash"),
125
+ }),
126
+ execute: async (params) => {
127
+ const gmail = await getGmailClient();
128
+ const { data } = await gmail.users.threads.trash({ userId: 'me', id: params.id });
129
+ return JSON.stringify(data);
130
+ },
131
+ });
132
+
133
+ server.addTool({
134
+ name: 'untrash_thread',
135
+ description: 'Remove a thread from the trash',
136
+ parameters: z.object({
137
+ id: z.string().describe("The ID of the thread to remove from trash"),
138
+ }),
139
+ execute: async (params) => {
140
+ const gmail = await getGmailClient();
141
+ const { data } = await gmail.users.threads.untrash({ userId: 'me', id: params.id });
142
+ return JSON.stringify(data);
143
+ },
144
+ });
145
+ }
@@ -1,8 +1,6 @@
1
- // Tool discovery and lazy-loading registration.
2
- //
3
- // Only the `load_google_tools` discovery tool is registered at startup.
4
- // When a category is loaded, its tools are dynamically registered and the
5
- // client is notified via tools/list_changed so it picks them up.
1
+ // Tool registration all categories loaded eagerly at startup.
2
+ // (Claude Code doesn't support notifications/tools/list_changed,
3
+ // so lazy-loading doesn't work.)
6
4
  import { z } from 'zod';
7
5
  import * as fs from 'fs/promises';
8
6
  import { getTokenPath } from '../auth.js';
@@ -10,11 +8,8 @@ import { resetClients } from '../clients.js';
10
8
  import { logger } from '../logger.js';
11
9
 
12
10
  // --- Category registry ---
13
- // Maps category names to { loader, description, toolCount }
14
11
  const CATEGORIES = {
15
12
  files: {
16
- description: 'Google Drive file management — list, search, create, copy, move, rename, delete files/folders, read file contents (pdf, docx), search within files',
17
- toolCount: 16,
18
13
  async loader(server) {
19
14
  const { registerDriveTools } = await import('./drive/index.js');
20
15
  const { registerExtrasTools } = await import('./extras/index.js');
@@ -23,8 +18,6 @@ const CATEGORIES = {
23
18
  },
24
19
  },
25
20
  documents: {
26
- description: 'Google Docs — read, write, insert text/tables/images, formatting, comments, tabs, markdown conversion',
27
- toolCount: 23,
28
21
  async loader(server) {
29
22
  const { registerDocsTools } = await import('./docs/index.js');
30
23
  const { registerUtilsTools } = await import('./utils/index.js');
@@ -33,16 +26,12 @@ const CATEGORIES = {
33
26
  },
34
27
  },
35
28
  spreadsheets: {
36
- description: 'Google Sheets — read, write, format cells, charts, tables, conditional formatting, data validation',
37
- toolCount: 30,
38
29
  async loader(server) {
39
30
  const { registerSheetsTools } = await import('./sheets/index.js');
40
31
  registerSheetsTools(server);
41
32
  },
42
33
  },
43
34
  email: {
44
- description: 'Gmail — send, reply, forward, get, list, delete, trash messages, create/update/send drafts, attachments, batch operations',
45
- toolCount: 19,
46
35
  async loader(server) {
47
36
  const { register: registerMessages } = await import('./gmail/messages.js');
48
37
  const { register: registerDrafts } = await import('./gmail/drafts.js');
@@ -51,94 +40,40 @@ const CATEGORIES = {
51
40
  },
52
41
  },
53
42
  email_threads: {
54
- description: 'Gmail threads — get, list, batch get, modify, delete, trash/untrash conversation threads',
55
- toolCount: 7,
56
43
  async loader(server) {
57
44
  const { register } = await import('./gmail/threads.js');
58
45
  register(server);
59
46
  },
60
47
  },
61
48
  email_labels: {
62
- description: 'Gmail labels — create, delete, get, list, update labels for organizing email',
63
- toolCount: 6,
64
49
  async loader(server) {
65
50
  const { register } = await import('./gmail/labels.js');
66
51
  register(server);
67
52
  },
68
53
  },
69
54
  email_settings: {
70
- description: 'Gmail settings — auto-forwarding, IMAP, POP, vacation responder, delegates, filters, forwarding addresses, send-as aliases, S/MIME, profile, mailbox watch',
71
- toolCount: 37,
72
55
  async loader(server) {
73
56
  const { register } = await import('./gmail/settings.js');
74
57
  register(server);
75
58
  },
76
59
  },
60
+ calendar: {
61
+ async loader(server) {
62
+ const { registerCalendarTools } = await import('./calendar/index.js');
63
+ registerCalendarTools(server);
64
+ },
65
+ },
77
66
  };
78
67
 
79
- // Track which categories have been loaded
80
- const loadedCategories = new Set();
81
-
82
68
  // ---------------------------------------------------------------------------
83
- // Notify the MCP client that the tool list has changed
69
+ // Public: register all tools eagerly, plus the logout utility.
84
70
  // ---------------------------------------------------------------------------
85
- function notifyToolsChanged(server) {
86
- try {
87
- const session = server.sessions?.[0];
88
- if (session?.server?.notification) {
89
- session.server.notification({ method: 'notifications/tools/list_changed' });
90
- logger.debug('Sent tools/list_changed notification.');
91
- } else {
92
- logger.debug('No session available for tools/list_changed notification.');
93
- }
94
- } catch (err) {
95
- logger.warn('Failed to send tools/list_changed notification:', err.message);
71
+ export async function registerAllTools(server) {
72
+ // Load every category
73
+ for (const [name, { loader }] of Object.entries(CATEGORIES)) {
74
+ await loader(server);
96
75
  }
97
- }
98
-
99
- // ---------------------------------------------------------------------------
100
- // Public: register the discovery tool (and logout)
101
- // ---------------------------------------------------------------------------
102
- export function registerAllTools(server) {
103
- // --- Discovery tool ---
104
- server.addTool({
105
- name: 'load_google_tools',
106
- description:
107
- 'Load Google Workspace tools by category. Call this first before using any Google service.\n\n' +
108
- 'Categories:\n' +
109
- Object.entries(CATEGORIES)
110
- .map(([name, { description, toolCount }]) => ` • ${name} (${toolCount} tools) — ${description}`)
111
- .join('\n') +
112
- '\n\nYou can load multiple categories at once by passing an array.',
113
- parameters: z.object({
114
- categories: z
115
- .array(z.enum(Object.keys(CATEGORIES)))
116
- .describe('One or more category names to load'),
117
- }),
118
- execute: async ({ categories }, { log }) => {
119
- const results = [];
120
- for (const cat of categories) {
121
- if (loadedCategories.has(cat)) {
122
- results.push({ category: cat, status: 'already_loaded' });
123
- continue;
124
- }
125
- log.info(`Loading category: ${cat}`);
126
- await CATEGORIES[cat].loader(server);
127
- loadedCategories.add(cat);
128
- results.push({
129
- category: cat,
130
- status: 'loaded',
131
- toolCount: CATEGORIES[cat].toolCount,
132
- });
133
- }
134
- notifyToolsChanged(server);
135
- return JSON.stringify({
136
- loaded: results,
137
- message: 'Tools are now available. You can call them directly.',
138
- all_loaded_categories: [...loadedCategories],
139
- });
140
- },
141
- });
76
+ logger.info(`Loaded all ${Object.keys(CATEGORIES).length} categories at startup.`);
142
77
 
143
78
  // --- Logout tool (always available) ---
144
79
  server.addTool({
@@ -0,0 +1,103 @@
1
+ // Gmail Label tools
2
+ import { z } from 'zod';
3
+ import { getGmailClient } from '../clients.js';
4
+
5
+ export function register(server) {
6
+ server.addTool({
7
+ name: 'create_label',
8
+ description: 'Create a new label',
9
+ parameters: z.object({
10
+ name: z.string().describe("The display name of the label"),
11
+ messageListVisibility: z.enum(['show', 'hide']).optional().describe("Visibility of messages with this label in the message list"),
12
+ labelListVisibility: z.enum(['labelShow', 'labelShowIfUnread', 'labelHide']).optional().describe("Visibility of the label in the label list"),
13
+ color: z.object({
14
+ textColor: z.string().describe("The text color as hex string"),
15
+ backgroundColor: z.string().describe("The background color as hex string"),
16
+ }).optional().describe("The color settings for the label"),
17
+ }),
18
+ execute: async (params) => {
19
+ const gmail = await getGmailClient();
20
+ const { data } = await gmail.users.labels.create({ userId: 'me', requestBody: params });
21
+ return JSON.stringify(data);
22
+ },
23
+ });
24
+
25
+ server.addTool({
26
+ name: 'delete_label',
27
+ description: 'Delete a label',
28
+ parameters: z.object({
29
+ id: z.string().describe("The ID of the label to delete"),
30
+ }),
31
+ execute: async (params) => {
32
+ const gmail = await getGmailClient();
33
+ const { data } = await gmail.users.labels.delete({ userId: 'me', id: params.id });
34
+ return JSON.stringify(data || { success: true });
35
+ },
36
+ });
37
+
38
+ server.addTool({
39
+ name: 'get_label',
40
+ description: 'Get a specific label by ID',
41
+ parameters: z.object({
42
+ id: z.string().describe("The ID of the label to retrieve"),
43
+ }),
44
+ execute: async (params) => {
45
+ const gmail = await getGmailClient();
46
+ const { data } = await gmail.users.labels.get({ userId: 'me', id: params.id });
47
+ return JSON.stringify(data);
48
+ },
49
+ });
50
+
51
+ server.addTool({
52
+ name: 'list_labels',
53
+ description: 'List all labels in the user\'s mailbox',
54
+ parameters: z.object({}),
55
+ execute: async () => {
56
+ const gmail = await getGmailClient();
57
+ const { data } = await gmail.users.labels.list({ userId: 'me' });
58
+ return JSON.stringify(data);
59
+ },
60
+ });
61
+
62
+ server.addTool({
63
+ name: 'patch_label',
64
+ description: 'Patch an existing label (partial update)',
65
+ parameters: z.object({
66
+ id: z.string().describe("The ID of the label to patch"),
67
+ name: z.string().optional().describe("The display name of the label"),
68
+ messageListVisibility: z.enum(['show', 'hide']).optional().describe("Visibility of messages with this label"),
69
+ labelListVisibility: z.enum(['labelShow', 'labelShowIfUnread', 'labelHide']).optional().describe("Visibility of the label in the label list"),
70
+ color: z.object({
71
+ textColor: z.string().describe("The text color as hex string"),
72
+ backgroundColor: z.string().describe("The background color as hex string"),
73
+ }).optional().describe("The color settings for the label"),
74
+ }),
75
+ execute: async (params) => {
76
+ const { id, ...labelData } = params;
77
+ const gmail = await getGmailClient();
78
+ const { data } = await gmail.users.labels.patch({ userId: 'me', id, requestBody: labelData });
79
+ return JSON.stringify(data);
80
+ },
81
+ });
82
+
83
+ server.addTool({
84
+ name: 'update_label',
85
+ description: 'Update an existing label',
86
+ parameters: z.object({
87
+ id: z.string().describe("The ID of the label to update"),
88
+ name: z.string().optional().describe("The display name of the label"),
89
+ messageListVisibility: z.enum(['show', 'hide']).optional().describe("Visibility of messages with this label"),
90
+ labelListVisibility: z.enum(['labelShow', 'labelShowIfUnread', 'labelHide']).optional().describe("Visibility of the label in the label list"),
91
+ color: z.object({
92
+ textColor: z.string().describe("The text color as hex string"),
93
+ backgroundColor: z.string().describe("The background color as hex string"),
94
+ }).optional().describe("The color settings for the label"),
95
+ }),
96
+ execute: async (params) => {
97
+ const { id, ...labelData } = params;
98
+ const gmail = await getGmailClient();
99
+ const { data } = await gmail.users.labels.update({ userId: 'me', id, requestBody: labelData });
100
+ return JSON.stringify(data);
101
+ },
102
+ });
103
+ }