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,399 @@
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
+ const accountEnum = z.enum(ACCOUNTS);
7
+ export function registerCalendarTools(server) {
8
+ server.registerTool('calendar_list_calendars', {
9
+ description: 'List all calendars for a Google account',
10
+ inputSchema: {
11
+ account: accountEnum.describe('Google account alias'),
12
+ },
13
+ }, async ({ account }) => {
14
+ try {
15
+ const auth = await getClient(account);
16
+ const cal = google.calendar({ version: 'v3', auth });
17
+ const res = await cal.calendarList.list();
18
+ const calendars = (res.data.items ?? []).map((c) => ({
19
+ id: c.id,
20
+ summary: c.summary,
21
+ description: c.description ?? '',
22
+ primary: c.primary ?? false,
23
+ timeZone: c.timeZone,
24
+ backgroundColor: c.backgroundColor,
25
+ }));
26
+ return {
27
+ content: [{ type: 'text', text: JSON.stringify(calendars, null, 2) }],
28
+ };
29
+ }
30
+ catch (error) {
31
+ return handleCalendarError(error, account);
32
+ }
33
+ });
34
+ server.registerTool('calendar_list_events', {
35
+ description: 'List events from a Google Calendar',
36
+ inputSchema: {
37
+ account: accountEnum.describe('Google account alias'),
38
+ calendarId: z.string().default('primary').optional()
39
+ .describe('Calendar ID (default: primary)'),
40
+ query: z.string().optional().describe('Free-text search query'),
41
+ timeMin: z.string().optional()
42
+ .describe('Start of time range (ISO 8601, e.g. "2026-04-04T00:00:00Z")'),
43
+ timeMax: z.string().optional()
44
+ .describe('End of time range (ISO 8601)'),
45
+ maxResults: z.number().min(1).max(250).default(25).optional()
46
+ .describe('Max events to return (default: 25)'),
47
+ },
48
+ }, async ({ account, calendarId, query, timeMin, timeMax, maxResults }) => {
49
+ try {
50
+ const auth = await getClient(account);
51
+ const cal = google.calendar({ version: 'v3', auth });
52
+ const params = {
53
+ calendarId: calendarId ?? 'primary',
54
+ maxResults: maxResults ?? 25,
55
+ singleEvents: true,
56
+ orderBy: 'startTime',
57
+ };
58
+ if (query)
59
+ params.q = query;
60
+ if (timeMin)
61
+ params.timeMin = timeMin;
62
+ if (timeMax)
63
+ params.timeMax = timeMax;
64
+ if (!timeMin && !timeMax) {
65
+ params.timeMin = new Date().toISOString();
66
+ }
67
+ const res = await cal.events.list(params);
68
+ const events = (res.data.items ?? []).map(formatEvent);
69
+ return {
70
+ content: [{ type: 'text', text: JSON.stringify(events, null, 2) }],
71
+ };
72
+ }
73
+ catch (error) {
74
+ return handleCalendarError(error, account);
75
+ }
76
+ });
77
+ server.registerTool('calendar_get_event', {
78
+ description: 'Get a single Google Calendar event by ID',
79
+ inputSchema: {
80
+ account: accountEnum.describe('Google account alias'),
81
+ eventId: z.string().describe('Calendar event ID'),
82
+ calendarId: z.string().default('primary').optional()
83
+ .describe('Calendar ID (default: primary)'),
84
+ },
85
+ }, async ({ account, eventId, calendarId }) => {
86
+ try {
87
+ const auth = await getClient(account);
88
+ const cal = google.calendar({ version: 'v3', auth });
89
+ const res = await cal.events.get({
90
+ calendarId: calendarId ?? 'primary',
91
+ eventId,
92
+ });
93
+ return {
94
+ content: [{ type: 'text', text: JSON.stringify(formatEvent(res.data), null, 2) }],
95
+ };
96
+ }
97
+ catch (error) {
98
+ return handleCalendarError(error, account);
99
+ }
100
+ });
101
+ server.registerTool('calendar_create_event', {
102
+ description: 'Create a Google Calendar event',
103
+ inputSchema: {
104
+ account: accountEnum.describe('Google account alias'),
105
+ summary: z.string().describe('Event title'),
106
+ start: z.string().describe('Start time (ISO 8601, e.g. "2026-04-05T10:00:00+01:00")'),
107
+ end: z.string().describe('End time (ISO 8601)'),
108
+ description: z.string().optional().describe('Event description'),
109
+ location: z.string().optional().describe('Event location'),
110
+ attendees: z.string().optional()
111
+ .describe('Comma-separated email addresses of attendees'),
112
+ calendarId: z.string().default('primary').optional()
113
+ .describe('Calendar ID (default: primary)'),
114
+ allDay: z.boolean().default(false).optional()
115
+ .describe('If true, start/end are dates (YYYY-MM-DD) not datetimes'),
116
+ },
117
+ }, async ({ account, summary, start, end, description, location, attendees, calendarId, allDay }) => {
118
+ try {
119
+ const auth = await getClient(account);
120
+ const cal = google.calendar({ version: 'v3', auth });
121
+ const event = { summary };
122
+ if (allDay) {
123
+ event.start = { date: start };
124
+ event.end = { date: end };
125
+ }
126
+ else {
127
+ event.start = { dateTime: start };
128
+ event.end = { dateTime: end };
129
+ }
130
+ if (description)
131
+ event.description = description;
132
+ if (location)
133
+ event.location = location;
134
+ if (attendees) {
135
+ event.attendees = attendees.split(',').map((e) => ({ email: e.trim() }));
136
+ }
137
+ const res = await cal.events.insert({
138
+ calendarId: calendarId ?? 'primary',
139
+ requestBody: event,
140
+ });
141
+ return {
142
+ content: [{
143
+ type: 'text',
144
+ text: JSON.stringify(formatEvent(res.data), null, 2),
145
+ }],
146
+ };
147
+ }
148
+ catch (error) {
149
+ return handleCalendarError(error, account);
150
+ }
151
+ });
152
+ server.registerTool('calendar_update_event', {
153
+ description: 'Update a Google Calendar event',
154
+ inputSchema: {
155
+ account: accountEnum.describe('Google account alias'),
156
+ eventId: z.string().describe('Calendar event ID'),
157
+ summary: z.string().optional().describe('New event title'),
158
+ start: z.string().optional().describe('New start time (ISO 8601)'),
159
+ end: z.string().optional().describe('New end time (ISO 8601)'),
160
+ description: z.string().optional().describe('New event description'),
161
+ location: z.string().optional().describe('New event location'),
162
+ attendees: z.string().optional()
163
+ .describe('Comma-separated email addresses (replaces existing attendees)'),
164
+ calendarId: z.string().default('primary').optional()
165
+ .describe('Calendar ID (default: primary)'),
166
+ },
167
+ }, async ({ account, eventId, summary, start, end, description, location, attendees, calendarId }) => {
168
+ try {
169
+ const auth = await getClient(account);
170
+ const cal = google.calendar({ version: 'v3', auth });
171
+ // Fetch the event first so a 404 surfaces before we attempt the patch.
172
+ await cal.events.get({
173
+ calendarId: calendarId ?? 'primary',
174
+ eventId,
175
+ });
176
+ const patch = {};
177
+ if (summary !== undefined)
178
+ patch.summary = summary;
179
+ if (description !== undefined)
180
+ patch.description = description;
181
+ if (location !== undefined)
182
+ patch.location = location;
183
+ if (start !== undefined) {
184
+ patch.start = start.length === 10
185
+ ? { date: start }
186
+ : { dateTime: start };
187
+ }
188
+ if (end !== undefined) {
189
+ patch.end = end.length === 10
190
+ ? { date: end }
191
+ : { dateTime: end };
192
+ }
193
+ if (attendees !== undefined) {
194
+ patch.attendees = attendees.split(',').map((e) => ({ email: e.trim() }));
195
+ }
196
+ const res = await cal.events.patch({
197
+ calendarId: calendarId ?? 'primary',
198
+ eventId,
199
+ requestBody: patch,
200
+ });
201
+ return {
202
+ content: [{
203
+ type: 'text',
204
+ text: JSON.stringify(formatEvent(res.data), null, 2),
205
+ }],
206
+ };
207
+ }
208
+ catch (error) {
209
+ return handleCalendarError(error, account);
210
+ }
211
+ });
212
+ server.registerTool('calendar_delete_event', {
213
+ description: 'Delete a Google Calendar event',
214
+ inputSchema: {
215
+ account: accountEnum.describe('Google account alias'),
216
+ eventId: z.string().describe('Calendar event ID'),
217
+ calendarId: z.string().default('primary').optional()
218
+ .describe('Calendar ID (default: primary)'),
219
+ },
220
+ }, async ({ account, eventId, calendarId }) => {
221
+ try {
222
+ const auth = await getClient(account);
223
+ const cal = google.calendar({ version: 'v3', auth });
224
+ await cal.events.delete({
225
+ calendarId: calendarId ?? 'primary',
226
+ eventId,
227
+ });
228
+ return {
229
+ content: [{ type: 'text', text: JSON.stringify({ deleted: true, eventId }, null, 2) }],
230
+ };
231
+ }
232
+ catch (error) {
233
+ return handleCalendarError(error, account);
234
+ }
235
+ });
236
+ server.registerTool('calendar_quick_add', {
237
+ description: 'Create a calendar event from a natural language string. Google parses date, time, title, and guests automatically.',
238
+ inputSchema: {
239
+ account: accountEnum.describe('Google account alias'),
240
+ calendarId: z.string().default('primary').optional()
241
+ .describe('Calendar ID (default: primary)'),
242
+ text: z.string().describe('Natural language event, e.g. "Lunch with Farouk Thursday 1pm at Le Boulanger"'),
243
+ sendNotifications: z.boolean().optional().describe('Send notifications to attendees (default: false)'),
244
+ },
245
+ }, async ({ account, calendarId, text, sendNotifications }) => {
246
+ try {
247
+ const auth = await getClient(account);
248
+ const cal = google.calendar({ version: 'v3', auth });
249
+ const res = await cal.events.quickAdd({
250
+ calendarId: calendarId ?? 'primary',
251
+ text,
252
+ sendNotifications: sendNotifications ?? false,
253
+ });
254
+ return {
255
+ content: [{ type: 'text', text: JSON.stringify(formatEvent(res.data), null, 2) }],
256
+ };
257
+ }
258
+ catch (error) {
259
+ return handleCalendarError(error, account);
260
+ }
261
+ });
262
+ server.registerTool('calendar_move_event', {
263
+ description: 'Move an event from one calendar to another',
264
+ inputSchema: {
265
+ account: accountEnum.describe('Google account alias'),
266
+ calendarId: z.string().describe('Source calendar ID'),
267
+ eventId: z.string().describe('Event ID to move'),
268
+ destinationCalendarId: z.string().describe('Destination calendar ID'),
269
+ sendNotifications: z.boolean().optional().describe('Send notifications (default: false)'),
270
+ },
271
+ }, async ({ account, calendarId, eventId, destinationCalendarId, sendNotifications }) => {
272
+ try {
273
+ const auth = await getClient(account);
274
+ const cal = google.calendar({ version: 'v3', auth });
275
+ const res = await cal.events.move({
276
+ calendarId,
277
+ eventId,
278
+ destination: destinationCalendarId,
279
+ sendNotifications: sendNotifications ?? false,
280
+ });
281
+ return {
282
+ content: [{ type: 'text', text: JSON.stringify(formatEvent(res.data), null, 2) }],
283
+ };
284
+ }
285
+ catch (error) {
286
+ return handleCalendarError(error, account);
287
+ }
288
+ });
289
+ server.registerTool('calendar_list_instances', {
290
+ description: 'List all occurrences of a recurring calendar event',
291
+ inputSchema: {
292
+ account: accountEnum.describe('Google account alias'),
293
+ calendarId: z.string().default('primary').optional()
294
+ .describe('Calendar ID (default: primary)'),
295
+ eventId: z.string().describe('ID of the recurring event series'),
296
+ timeMin: z.string().optional().describe('ISO 8601 — filter instances after this time'),
297
+ timeMax: z.string().optional().describe('ISO 8601 — filter instances before this time'),
298
+ maxResults: z.number().min(1).max(250).default(25).optional()
299
+ .describe('Max instances to return (default: 25)'),
300
+ },
301
+ }, async ({ account, calendarId, eventId, timeMin, timeMax, maxResults }) => {
302
+ try {
303
+ const auth = await getClient(account);
304
+ const cal = google.calendar({ version: 'v3', auth });
305
+ const res = await cal.events.instances({
306
+ calendarId: calendarId ?? 'primary',
307
+ eventId,
308
+ timeMin,
309
+ timeMax,
310
+ maxResults: maxResults ?? 25,
311
+ });
312
+ const events = (res.data.items ?? []).map(formatEvent);
313
+ return {
314
+ content: [{ type: 'text', text: JSON.stringify(events, null, 2) }],
315
+ };
316
+ }
317
+ catch (error) {
318
+ return handleCalendarError(error, account);
319
+ }
320
+ });
321
+ server.registerTool('calendar_get_freebusy', {
322
+ description: 'Check free/busy times for one or more calendars within a time window. Returns only busy blocks, not event details.',
323
+ inputSchema: {
324
+ account: accountEnum.describe('Google account alias'),
325
+ calendarIds: z.array(z.string()).describe('Calendar IDs to check, e.g. ["primary", "user@example.com"]'),
326
+ timeMin: z.string().describe('ISO 8601 start of window'),
327
+ timeMax: z.string().describe('ISO 8601 end of window'),
328
+ timeZone: z.string().optional().describe('Timezone (default: UTC)'),
329
+ },
330
+ }, async ({ account, calendarIds, timeMin, timeMax, timeZone }) => {
331
+ try {
332
+ const auth = await getClient(account);
333
+ const cal = google.calendar({ version: 'v3', auth });
334
+ const res = await cal.freebusy.query({
335
+ requestBody: {
336
+ timeMin,
337
+ timeMax,
338
+ timeZone: timeZone ?? 'UTC',
339
+ items: calendarIds.map((id) => ({ id })),
340
+ },
341
+ });
342
+ return {
343
+ content: [{ type: 'text', text: JSON.stringify(res.data.calendars, null, 2) }],
344
+ };
345
+ }
346
+ catch (error) {
347
+ return handleCalendarError(error, account);
348
+ }
349
+ });
350
+ server.registerTool('calendar_create_calendar', {
351
+ description: 'Create a new calendar under the account',
352
+ inputSchema: {
353
+ account: accountEnum.describe('Google account alias'),
354
+ summary: z.string().describe('Calendar name'),
355
+ description: z.string().optional().describe('Calendar description'),
356
+ timeZone: z.string().optional().describe('Timezone (default: UTC)'),
357
+ },
358
+ }, async ({ account, summary, description, timeZone }) => {
359
+ try {
360
+ const auth = await getClient(account);
361
+ const cal = google.calendar({ version: 'v3', auth });
362
+ const res = await cal.calendars.insert({
363
+ requestBody: {
364
+ summary,
365
+ description,
366
+ timeZone: timeZone ?? 'UTC',
367
+ },
368
+ });
369
+ return {
370
+ content: [{ type: 'text', text: JSON.stringify(res.data, null, 2) }],
371
+ };
372
+ }
373
+ catch (error) {
374
+ return handleCalendarError(error, account);
375
+ }
376
+ });
377
+ }
378
+ function formatEvent(event) {
379
+ return {
380
+ id: event.id,
381
+ summary: event.summary ?? '',
382
+ description: event.description ?? '',
383
+ location: event.location ?? '',
384
+ start: event.start?.dateTime ?? event.start?.date ?? '',
385
+ end: event.end?.dateTime ?? event.end?.date ?? '',
386
+ status: event.status,
387
+ htmlLink: event.htmlLink,
388
+ organizer: event.organizer?.email ?? '',
389
+ attendees: (event.attendees ?? []).map((a) => ({
390
+ email: a.email,
391
+ responseStatus: a.responseStatus,
392
+ })),
393
+ created: event.created,
394
+ updated: event.updated,
395
+ };
396
+ }
397
+ function handleCalendarError(error, account) {
398
+ return handleGoogleApiError(error, account);
399
+ }
@@ -0,0 +1,2 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerChatTools(server: McpServer): void;
@@ -0,0 +1,121 @@
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
+ const accountEnum = z.enum(ACCOUNTS);
7
+ export function registerChatTools(server) {
8
+ server.registerTool('chat_spaces_list', {
9
+ description: 'List Google Chat spaces the user is a member of',
10
+ inputSchema: {
11
+ account: accountEnum.describe('Google account alias'),
12
+ pageSize: z.number().min(1).max(1000).optional(),
13
+ pageToken: z.string().optional(),
14
+ filter: z.string().optional().describe('Filter expression, e.g. "spaceType = \\"DIRECT_MESSAGE\\""'),
15
+ },
16
+ }, async ({ account, pageSize, pageToken, filter }) => {
17
+ try {
18
+ const auth = await getClient(account);
19
+ const chat = google.chat({ version: 'v1', auth });
20
+ const res = await chat.spaces.list({
21
+ pageSize: pageSize ?? 100,
22
+ pageToken,
23
+ filter,
24
+ });
25
+ return {
26
+ content: [{ type: 'text', text: JSON.stringify(res.data, null, 2) }],
27
+ };
28
+ }
29
+ catch (error) {
30
+ return handleChatError(error, account);
31
+ }
32
+ });
33
+ server.registerTool('chat_spaces_get', {
34
+ description: 'Get details about a single Chat space',
35
+ inputSchema: {
36
+ account: accountEnum.describe('Google account alias'),
37
+ name: z.string().describe('Space resource name, format: spaces/{space}'),
38
+ },
39
+ }, async ({ account, name }) => {
40
+ try {
41
+ const auth = await getClient(account);
42
+ const chat = google.chat({ version: 'v1', auth });
43
+ const res = await chat.spaces.get({ name });
44
+ return {
45
+ content: [{ type: 'text', text: JSON.stringify(res.data, null, 2) }],
46
+ };
47
+ }
48
+ catch (error) {
49
+ return handleChatError(error, account);
50
+ }
51
+ });
52
+ server.registerTool('chat_messages_create', {
53
+ description: 'Send a message into a Chat space. Supply plain text or a Card v2 in cardsV2.',
54
+ inputSchema: {
55
+ account: accountEnum.describe('Google account alias'),
56
+ parent: z.string().describe('Space resource name, format: spaces/{space}'),
57
+ text: z.string().optional().describe('Plain message text'),
58
+ cardsV2: z.array(z.record(z.string(), z.any())).optional().describe('Optional Card v2 payloads'),
59
+ threadKey: z.string().optional().describe('Thread key to group messages'),
60
+ messageReplyOption: z.enum(['MESSAGE_REPLY_OPTION_UNSPECIFIED', 'REPLY_MESSAGE_FALLBACK_TO_NEW_THREAD', 'REPLY_MESSAGE_OR_FAIL']).optional(),
61
+ },
62
+ }, async ({ account, parent, text, cardsV2, threadKey, messageReplyOption }) => {
63
+ try {
64
+ if (!text && (!cardsV2 || cardsV2.length === 0)) {
65
+ return { content: [{ type: 'text', text: JSON.stringify({ error: 'Either text or cardsV2 must be provided' }) }], isError: true };
66
+ }
67
+ const auth = await getClient(account);
68
+ const chat = google.chat({ version: 'v1', auth });
69
+ const requestBody = {};
70
+ if (text)
71
+ requestBody.text = text;
72
+ if (cardsV2)
73
+ requestBody.cardsV2 = cardsV2;
74
+ if (threadKey)
75
+ requestBody.thread = { threadKey };
76
+ const res = await chat.spaces.messages.create({
77
+ parent,
78
+ messageReplyOption,
79
+ requestBody,
80
+ });
81
+ return {
82
+ content: [{ type: 'text', text: JSON.stringify(res.data, null, 2) }],
83
+ };
84
+ }
85
+ catch (error) {
86
+ return handleChatError(error, account);
87
+ }
88
+ });
89
+ server.registerTool('chat_messages_list', {
90
+ description: 'List messages in a Chat space',
91
+ inputSchema: {
92
+ account: accountEnum.describe('Google account alias'),
93
+ parent: z.string().describe('Space resource name, format: spaces/{space}'),
94
+ pageSize: z.number().min(1).max(1000).optional(),
95
+ pageToken: z.string().optional(),
96
+ filter: z.string().optional().describe('Filter expression, e.g. "createTime > \\"2026-01-01T00:00:00Z\\""'),
97
+ orderBy: z.string().optional().describe('e.g. "createTime DESC"'),
98
+ },
99
+ }, async ({ account, parent, pageSize, pageToken, filter, orderBy }) => {
100
+ try {
101
+ const auth = await getClient(account);
102
+ const chat = google.chat({ version: 'v1', auth });
103
+ const res = await chat.spaces.messages.list({
104
+ parent,
105
+ pageSize: pageSize ?? 100,
106
+ pageToken,
107
+ filter,
108
+ orderBy,
109
+ });
110
+ return {
111
+ content: [{ type: 'text', text: JSON.stringify(res.data, null, 2) }],
112
+ };
113
+ }
114
+ catch (error) {
115
+ return handleChatError(error, account);
116
+ }
117
+ });
118
+ }
119
+ function handleChatError(error, account) {
120
+ return handleGoogleApiError(error, account, "Chat tools require the optional \"chat\" scope bundle. Add GOOGLE_OPTIONAL_SCOPES=chat and re-auth.");
121
+ }
@@ -0,0 +1,2 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerContactsTools(server: McpServer): void;