@goforgeit/mcp-google-workspace 0.4.11

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,32 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+
3
+ /**
4
+ * @goforgeit/mcp-google-workspace — MCP server for Google Workspace
5
+ *
6
+ * Provides Gmail, Calendar, and Drive tools via stdio transport.
7
+ * Reads Forge's OAuth tokens and auto-refreshes them.
8
+ */
9
+
10
+ type ToolGroup = 'gmail' | 'calendar' | 'chat' | 'docs' | 'sheets' | 'drive';
11
+ interface GoogleWorkspaceMCPOptions {
12
+ tokenPath: string;
13
+ clientId: string;
14
+ clientSecret: string;
15
+ enabledGroups?: ToolGroup[];
16
+ }
17
+ interface ToolHandler {
18
+ handler: (args: any) => Promise<{
19
+ content: Array<{
20
+ type: 'text';
21
+ text: string;
22
+ }>;
23
+ }>;
24
+ }
25
+ interface GoogleWorkspaceMCPServer {
26
+ server: McpServer;
27
+ getRegisteredTools(): Record<string, ToolHandler>;
28
+ start(): Promise<void>;
29
+ }
30
+ declare function createGoogleWorkspaceMCPServer(options: GoogleWorkspaceMCPOptions): GoogleWorkspaceMCPServer;
31
+
32
+ export { type GoogleWorkspaceMCPOptions, type ToolGroup, createGoogleWorkspaceMCPServer };
package/dist/index.js ADDED
@@ -0,0 +1,1093 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
+ import { z as z2 } from "zod";
7
+ import { google } from "googleapis";
8
+
9
+ // ../shared/dist/mcp-auth/forge-token-manager.js
10
+ import * as fs from "fs/promises";
11
+ import * as path from "path";
12
+ import { z } from "zod";
13
+ var ForgeOAuthTokensSchema = z.object({
14
+ accessToken: z.string(),
15
+ refreshToken: z.string().optional(),
16
+ expiresAt: z.number(),
17
+ providerId: z.string(),
18
+ connectedAt: z.string(),
19
+ scope: z.string().optional(),
20
+ email: z.string().optional(),
21
+ accountId: z.string().optional()
22
+ });
23
+ var DEFAULT_REFRESH_BUFFER_MS = 5 * 60 * 1e3;
24
+ function createForgeTokenManager(options) {
25
+ const { tokenPath, clientId, clientSecret, tokenUrl, refreshBufferMs = DEFAULT_REFRESH_BUFFER_MS } = options;
26
+ let cachedTokens = null;
27
+ const refreshCallbacks = [];
28
+ function isExpiredOrNearExpiry(tokens) {
29
+ return Date.now() >= tokens.expiresAt - refreshBufferMs;
30
+ }
31
+ async function readTokenFile() {
32
+ let raw;
33
+ try {
34
+ raw = await fs.readFile(tokenPath, "utf-8");
35
+ } catch (err) {
36
+ if (err && typeof err === "object" && "code" in err && err.code === "ENOENT") {
37
+ throw new Error(`Token file not found: ${tokenPath}`);
38
+ }
39
+ throw err;
40
+ }
41
+ let parsed;
42
+ try {
43
+ parsed = JSON.parse(raw);
44
+ } catch {
45
+ throw new Error(`Invalid JSON in token file: ${tokenPath}`);
46
+ }
47
+ const result = ForgeOAuthTokensSchema.safeParse(parsed);
48
+ if (!result.success) {
49
+ throw new Error(`Invalid token file format: ${result.error.message}`);
50
+ }
51
+ return result.data;
52
+ }
53
+ async function refreshToken(tokens) {
54
+ if (!tokens.refreshToken) {
55
+ throw new Error("No refresh token available \u2014 cannot refresh expired token");
56
+ }
57
+ const body = new URLSearchParams({
58
+ grant_type: "refresh_token",
59
+ refresh_token: tokens.refreshToken,
60
+ client_id: clientId,
61
+ client_secret: clientSecret
62
+ });
63
+ const response = await fetch(tokenUrl, {
64
+ method: "POST",
65
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
66
+ body: body.toString()
67
+ });
68
+ if (!response.ok) {
69
+ const errorText = await response.text();
70
+ throw new Error(`Token refresh failed (${response.status}): ${errorText}`);
71
+ }
72
+ const data = await response.json();
73
+ const refreshedTokens = {
74
+ ...tokens,
75
+ accessToken: data.access_token,
76
+ refreshToken: data.refresh_token || tokens.refreshToken,
77
+ expiresAt: Date.now() + data.expires_in * 1e3
78
+ };
79
+ await fs.mkdir(path.dirname(tokenPath), { recursive: true });
80
+ await fs.writeFile(tokenPath, JSON.stringify(refreshedTokens), { mode: 384 });
81
+ for (const cb of refreshCallbacks) {
82
+ cb(refreshedTokens);
83
+ }
84
+ return refreshedTokens;
85
+ }
86
+ return {
87
+ async getAccessToken() {
88
+ if (cachedTokens && !isExpiredOrNearExpiry(cachedTokens)) {
89
+ return cachedTokens.accessToken;
90
+ }
91
+ const tokens = cachedTokens || await readTokenFile();
92
+ if (isExpiredOrNearExpiry(tokens)) {
93
+ if (tokens.refreshToken) {
94
+ cachedTokens = await refreshToken(tokens);
95
+ } else {
96
+ cachedTokens = tokens;
97
+ }
98
+ } else {
99
+ cachedTokens = tokens;
100
+ }
101
+ return cachedTokens.accessToken;
102
+ },
103
+ getTokenInfo() {
104
+ return cachedTokens;
105
+ },
106
+ onRefresh(callback) {
107
+ refreshCallbacks.push(callback);
108
+ }
109
+ };
110
+ }
111
+
112
+ // src/index.ts
113
+ var GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token";
114
+ function createGoogleWorkspaceMCPServer(options) {
115
+ const tokenManager = createForgeTokenManager({
116
+ tokenPath: options.tokenPath,
117
+ clientId: options.clientId,
118
+ clientSecret: options.clientSecret,
119
+ tokenUrl: GOOGLE_TOKEN_URL
120
+ });
121
+ const server = new McpServer({
122
+ name: "@goforgeit/mcp-google-workspace",
123
+ version: "0.1.0"
124
+ });
125
+ const registeredTools = {};
126
+ async function getAuthClient() {
127
+ const token = await tokenManager.getAccessToken();
128
+ const auth = new google.auth.OAuth2();
129
+ auth.setCredentials({ access_token: token });
130
+ return auth;
131
+ }
132
+ const enabledGroups = options.enabledGroups ? new Set(options.enabledGroups) : null;
133
+ let currentGroup = "gmail";
134
+ function registerTool(name, description, schema, handler) {
135
+ if (enabledGroups && !enabledGroups.has(currentGroup)) return;
136
+ registeredTools[name] = { handler };
137
+ server.tool(name, description, schema, handler);
138
+ }
139
+ currentGroup = "gmail";
140
+ registerTool(
141
+ "search_emails",
142
+ "Search Gmail messages by query (uses Gmail search syntax like is:unread, from:, subject:)",
143
+ {
144
+ query: z2.string().describe('Gmail search query (e.g. "is:unread", "from:user@example.com")'),
145
+ maxResults: z2.number().optional().default(10).describe("Maximum number of results (default 10)")
146
+ },
147
+ async (args) => {
148
+ const auth = await getAuthClient();
149
+ const gmail = google.gmail({ version: "v1", auth });
150
+ const listRes = await gmail.users.messages.list({
151
+ userId: "me",
152
+ q: args.query,
153
+ maxResults: args.maxResults || 10
154
+ });
155
+ const messages = listRes.data.messages || [];
156
+ const detailed = await Promise.all(
157
+ messages.map(async (msg) => {
158
+ const detail = await gmail.users.messages.get({
159
+ userId: "me",
160
+ id: msg.id,
161
+ format: "metadata",
162
+ metadataHeaders: ["Subject", "From", "Date"]
163
+ });
164
+ const headers = detail.data.payload?.headers || [];
165
+ return {
166
+ id: detail.data.id,
167
+ subject: headers.find((h) => h.name === "Subject")?.value,
168
+ from: headers.find((h) => h.name === "From")?.value,
169
+ date: headers.find((h) => h.name === "Date")?.value,
170
+ snippet: detail.data.snippet,
171
+ labelIds: detail.data.labelIds
172
+ };
173
+ })
174
+ );
175
+ return {
176
+ content: [
177
+ {
178
+ type: "text",
179
+ text: JSON.stringify({
180
+ messages: detailed,
181
+ resultSizeEstimate: listRes.data.resultSizeEstimate
182
+ })
183
+ }
184
+ ]
185
+ };
186
+ }
187
+ );
188
+ registerTool(
189
+ "get_email",
190
+ "Get full email details by message ID",
191
+ {
192
+ messageId: z2.string().describe("Gmail message ID")
193
+ },
194
+ async (args) => {
195
+ const auth = await getAuthClient();
196
+ const gmail = google.gmail({ version: "v1", auth });
197
+ const detail = await gmail.users.messages.get({
198
+ userId: "me",
199
+ id: args.messageId,
200
+ format: "full"
201
+ });
202
+ const headers = detail.data.payload?.headers || [];
203
+ return {
204
+ content: [
205
+ {
206
+ type: "text",
207
+ text: JSON.stringify({
208
+ id: detail.data.id,
209
+ subject: headers.find((h) => h.name === "Subject")?.value,
210
+ from: headers.find((h) => h.name === "From")?.value,
211
+ to: headers.find((h) => h.name === "To")?.value,
212
+ date: headers.find((h) => h.name === "Date")?.value,
213
+ snippet: detail.data.snippet,
214
+ labelIds: detail.data.labelIds,
215
+ body: detail.data.payload?.body?.data
216
+ })
217
+ }
218
+ ]
219
+ };
220
+ }
221
+ );
222
+ registerTool("list_labels", "List all Gmail labels", {}, async () => {
223
+ const auth = await getAuthClient();
224
+ const gmail = google.gmail({ version: "v1", auth });
225
+ const res = await gmail.users.labels.list({ userId: "me" });
226
+ return {
227
+ content: [
228
+ {
229
+ type: "text",
230
+ text: JSON.stringify({ labels: res.data.labels || [] })
231
+ }
232
+ ]
233
+ };
234
+ });
235
+ currentGroup = "calendar";
236
+ registerTool(
237
+ "list_events",
238
+ "List calendar events in a time range",
239
+ {
240
+ timeMin: z2.string().optional().describe("Start time (ISO 8601, e.g. 2026-02-28T00:00:00Z)"),
241
+ timeMax: z2.string().optional().describe("End time (ISO 8601)"),
242
+ calendarId: z2.string().optional().default("primary").describe("Calendar ID (default: primary)"),
243
+ maxResults: z2.number().optional().default(25).describe("Maximum events to return")
244
+ },
245
+ async (args) => {
246
+ const auth = await getAuthClient();
247
+ const calendar = google.calendar({ version: "v3", auth });
248
+ const res = await calendar.events.list({
249
+ calendarId: args.calendarId || "primary",
250
+ timeMin: args.timeMin,
251
+ timeMax: args.timeMax,
252
+ maxResults: args.maxResults || 25,
253
+ singleEvents: true,
254
+ orderBy: "startTime"
255
+ });
256
+ const events = (res.data.items || []).map((event) => ({
257
+ id: event.id,
258
+ summary: event.summary,
259
+ description: event.description,
260
+ start: event.start,
261
+ end: event.end,
262
+ location: event.location,
263
+ attendees: event.attendees?.map((a) => ({
264
+ email: a.email,
265
+ responseStatus: a.responseStatus
266
+ })),
267
+ htmlLink: event.htmlLink
268
+ }));
269
+ return {
270
+ content: [{ type: "text", text: JSON.stringify({ events }) }]
271
+ };
272
+ }
273
+ );
274
+ registerTool(
275
+ "get_event",
276
+ "Get a specific calendar event by ID",
277
+ {
278
+ eventId: z2.string().describe("Calendar event ID"),
279
+ calendarId: z2.string().optional().default("primary").describe("Calendar ID (default: primary)")
280
+ },
281
+ async (args) => {
282
+ const auth = await getAuthClient();
283
+ const calendar = google.calendar({ version: "v3", auth });
284
+ const res = await calendar.events.get({
285
+ calendarId: args.calendarId || "primary",
286
+ eventId: args.eventId
287
+ });
288
+ return {
289
+ content: [
290
+ {
291
+ type: "text",
292
+ text: JSON.stringify({
293
+ id: res.data.id,
294
+ summary: res.data.summary,
295
+ description: res.data.description,
296
+ start: res.data.start,
297
+ end: res.data.end,
298
+ location: res.data.location,
299
+ attendees: res.data.attendees,
300
+ htmlLink: res.data.htmlLink
301
+ })
302
+ }
303
+ ]
304
+ };
305
+ }
306
+ );
307
+ registerTool("list_calendars", "List all calendars the user has access to", {}, async () => {
308
+ const auth = await getAuthClient();
309
+ const calendar = google.calendar({ version: "v3", auth });
310
+ const res = await calendar.calendarList.list();
311
+ const calendars = (res.data.items || []).map((cal) => ({
312
+ id: cal.id,
313
+ summary: cal.summary,
314
+ primary: cal.primary,
315
+ accessRole: cal.accessRole,
316
+ backgroundColor: cal.backgroundColor
317
+ }));
318
+ return {
319
+ content: [{ type: "text", text: JSON.stringify({ calendars }) }]
320
+ };
321
+ });
322
+ currentGroup = "gmail";
323
+ registerTool(
324
+ "create_draft",
325
+ "Create a Gmail draft email",
326
+ {
327
+ to: z2.string().describe("Recipient email address"),
328
+ subject: z2.string().describe("Email subject"),
329
+ body: z2.string().describe("Email body (plain text)"),
330
+ cc: z2.string().optional().describe("CC email address"),
331
+ bcc: z2.string().optional().describe("BCC email address")
332
+ },
333
+ async (args) => {
334
+ const auth = await getAuthClient();
335
+ const gmail = google.gmail({ version: "v1", auth });
336
+ const headers = [
337
+ `To: ${args.to}`,
338
+ `Subject: ${args.subject}`,
339
+ "Content-Type: text/plain; charset=utf-8"
340
+ ];
341
+ if (args.cc) headers.push(`Cc: ${args.cc}`);
342
+ if (args.bcc) headers.push(`Bcc: ${args.bcc}`);
343
+ const raw = Buffer.from(headers.join("\r\n") + "\r\n\r\n" + args.body).toString("base64url");
344
+ const res = await gmail.users.drafts.create({
345
+ userId: "me",
346
+ requestBody: { message: { raw } }
347
+ });
348
+ return {
349
+ content: [
350
+ {
351
+ type: "text",
352
+ text: JSON.stringify({ draftId: res.data.id, messageId: res.data.message?.id })
353
+ }
354
+ ]
355
+ };
356
+ }
357
+ );
358
+ registerTool(
359
+ "archive_email",
360
+ "Archive a Gmail message (removes from Inbox)",
361
+ {
362
+ messageId: z2.string().describe("Gmail message ID to archive")
363
+ },
364
+ async (args) => {
365
+ const auth = await getAuthClient();
366
+ const gmail = google.gmail({ version: "v1", auth });
367
+ await gmail.users.messages.modify({
368
+ userId: "me",
369
+ id: args.messageId,
370
+ requestBody: { removeLabelIds: ["INBOX"] }
371
+ });
372
+ return {
373
+ content: [
374
+ {
375
+ type: "text",
376
+ text: JSON.stringify({ archived: true, messageId: args.messageId })
377
+ }
378
+ ]
379
+ };
380
+ }
381
+ );
382
+ currentGroup = "calendar";
383
+ registerTool(
384
+ "create_event",
385
+ "Create a new Google Calendar event",
386
+ {
387
+ summary: z2.string().describe("Event title"),
388
+ startDateTime: z2.string().describe("Start time (ISO 8601, e.g. 2026-03-01T10:00:00-07:00)"),
389
+ endDateTime: z2.string().describe("End time (ISO 8601)"),
390
+ description: z2.string().optional().describe("Event description"),
391
+ location: z2.string().optional().describe("Event location"),
392
+ attendeeEmails: z2.array(z2.string()).optional().describe("Attendee email addresses"),
393
+ calendarId: z2.string().optional().default("primary").describe("Calendar ID (default: primary)")
394
+ },
395
+ async (args) => {
396
+ const auth = await getAuthClient();
397
+ const calendar = google.calendar({ version: "v3", auth });
398
+ const res = await calendar.events.insert({
399
+ calendarId: args.calendarId || "primary",
400
+ requestBody: {
401
+ summary: args.summary,
402
+ description: args.description,
403
+ location: args.location,
404
+ start: { dateTime: args.startDateTime },
405
+ end: { dateTime: args.endDateTime },
406
+ attendees: args.attendeeEmails?.map((email) => ({ email }))
407
+ }
408
+ });
409
+ return {
410
+ content: [
411
+ {
412
+ type: "text",
413
+ text: JSON.stringify({
414
+ eventId: res.data.id,
415
+ htmlLink: res.data.htmlLink,
416
+ summary: res.data.summary
417
+ })
418
+ }
419
+ ]
420
+ };
421
+ }
422
+ );
423
+ registerTool(
424
+ "update_event",
425
+ "Update an existing Google Calendar event",
426
+ {
427
+ eventId: z2.string().describe("Calendar event ID"),
428
+ summary: z2.string().optional().describe("New event title"),
429
+ startDateTime: z2.string().optional().describe("New start time (ISO 8601)"),
430
+ endDateTime: z2.string().optional().describe("New end time (ISO 8601)"),
431
+ description: z2.string().optional().describe("New event description"),
432
+ location: z2.string().optional().describe("New event location"),
433
+ calendarId: z2.string().optional().default("primary").describe("Calendar ID (default: primary)")
434
+ },
435
+ async (args) => {
436
+ const auth = await getAuthClient();
437
+ const calendar = google.calendar({ version: "v3", auth });
438
+ const patch = {};
439
+ if (args.summary) patch.summary = args.summary;
440
+ if (args.description !== void 0) patch.description = args.description;
441
+ if (args.location !== void 0) patch.location = args.location;
442
+ if (args.startDateTime) patch.start = { dateTime: args.startDateTime };
443
+ if (args.endDateTime) patch.end = { dateTime: args.endDateTime };
444
+ const res = await calendar.events.patch({
445
+ calendarId: args.calendarId || "primary",
446
+ eventId: args.eventId,
447
+ requestBody: patch
448
+ });
449
+ return {
450
+ content: [
451
+ {
452
+ type: "text",
453
+ text: JSON.stringify({
454
+ eventId: res.data.id,
455
+ summary: res.data.summary,
456
+ updated: true
457
+ })
458
+ }
459
+ ]
460
+ };
461
+ }
462
+ );
463
+ registerTool(
464
+ "delete_event",
465
+ "Delete a Google Calendar event",
466
+ {
467
+ eventId: z2.string().describe("Calendar event ID to delete"),
468
+ calendarId: z2.string().optional().default("primary").describe("Calendar ID (default: primary)")
469
+ },
470
+ async (args) => {
471
+ const auth = await getAuthClient();
472
+ const calendar = google.calendar({ version: "v3", auth });
473
+ await calendar.events.delete({
474
+ calendarId: args.calendarId || "primary",
475
+ eventId: args.eventId
476
+ });
477
+ return {
478
+ content: [
479
+ {
480
+ type: "text",
481
+ text: JSON.stringify({ deleted: true, eventId: args.eventId })
482
+ }
483
+ ]
484
+ };
485
+ }
486
+ );
487
+ currentGroup = "chat";
488
+ registerTool("list_spaces", "List Google Chat spaces (rooms, DMs, group chats)", {}, async () => {
489
+ const auth = await getAuthClient();
490
+ const chat = google.chat({ version: "v1", auth });
491
+ const res = await chat.spaces.list();
492
+ const spaces = (res.data.spaces || []).map((space) => ({
493
+ name: space.name,
494
+ displayName: space.displayName,
495
+ type: space.type,
496
+ singleUserBotDm: space.singleUserBotDm
497
+ }));
498
+ return {
499
+ content: [{ type: "text", text: JSON.stringify({ spaces }) }]
500
+ };
501
+ });
502
+ registerTool(
503
+ "get_space_messages",
504
+ "Get recent messages from a Google Chat space",
505
+ {
506
+ spaceName: z2.string().describe("Space resource name (e.g. spaces/abc123)"),
507
+ maxResults: z2.number().optional().default(25).describe("Maximum messages to return")
508
+ },
509
+ async (args) => {
510
+ const auth = await getAuthClient();
511
+ const chat = google.chat({ version: "v1", auth });
512
+ const res = await chat.spaces.messages.list({
513
+ parent: args.spaceName,
514
+ pageSize: args.maxResults || 25
515
+ });
516
+ const messages = (res.data.messages || []).map((msg) => ({
517
+ name: msg.name,
518
+ sender: msg.sender,
519
+ text: msg.text,
520
+ createTime: msg.createTime
521
+ }));
522
+ return {
523
+ content: [{ type: "text", text: JSON.stringify({ messages }) }]
524
+ };
525
+ }
526
+ );
527
+ registerTool(
528
+ "send_chat_message",
529
+ "Send a message to a Google Chat space",
530
+ {
531
+ spaceName: z2.string().describe("Space resource name (e.g. spaces/abc123)"),
532
+ text: z2.string().describe("Message text to send")
533
+ },
534
+ async (args) => {
535
+ const auth = await getAuthClient();
536
+ const chat = google.chat({ version: "v1", auth });
537
+ const res = await chat.spaces.messages.create({
538
+ parent: args.spaceName,
539
+ requestBody: { text: args.text }
540
+ });
541
+ return {
542
+ content: [
543
+ {
544
+ type: "text",
545
+ text: JSON.stringify({
546
+ messageName: res.data.name,
547
+ text: res.data.text,
548
+ createTime: res.data.createTime
549
+ })
550
+ }
551
+ ]
552
+ };
553
+ }
554
+ );
555
+ currentGroup = "docs";
556
+ registerTool(
557
+ "get_document",
558
+ "Get a Google Doc by document ID (returns structured content)",
559
+ {
560
+ documentId: z2.string().describe("Google Docs document ID")
561
+ },
562
+ async (args) => {
563
+ const auth = await getAuthClient();
564
+ const docs = google.docs({ version: "v1", auth });
565
+ const res = await docs.documents.get({ documentId: args.documentId });
566
+ return {
567
+ content: [
568
+ {
569
+ type: "text",
570
+ text: JSON.stringify({
571
+ documentId: res.data.documentId,
572
+ title: res.data.title,
573
+ body: res.data.body
574
+ })
575
+ }
576
+ ]
577
+ };
578
+ }
579
+ );
580
+ registerTool(
581
+ "create_document",
582
+ "Create a new Google Doc",
583
+ {
584
+ title: z2.string().describe("Document title")
585
+ },
586
+ async (args) => {
587
+ const auth = await getAuthClient();
588
+ const docs = google.docs({ version: "v1", auth });
589
+ const res = await docs.documents.create({
590
+ requestBody: { title: args.title }
591
+ });
592
+ return {
593
+ content: [
594
+ {
595
+ type: "text",
596
+ text: JSON.stringify({
597
+ documentId: res.data.documentId,
598
+ title: res.data.title
599
+ })
600
+ }
601
+ ]
602
+ };
603
+ }
604
+ );
605
+ currentGroup = "sheets";
606
+ registerTool(
607
+ "get_spreadsheet",
608
+ "Get Google Sheets spreadsheet metadata and sheet names",
609
+ {
610
+ spreadsheetId: z2.string().describe("Google Sheets spreadsheet ID")
611
+ },
612
+ async (args) => {
613
+ const auth = await getAuthClient();
614
+ const sheets = google.sheets({ version: "v4", auth });
615
+ const res = await sheets.spreadsheets.get({
616
+ spreadsheetId: args.spreadsheetId
617
+ });
618
+ const sheetList = (res.data.sheets || []).map((s) => ({
619
+ sheetId: s.properties?.sheetId,
620
+ title: s.properties?.title,
621
+ index: s.properties?.index
622
+ }));
623
+ return {
624
+ content: [
625
+ {
626
+ type: "text",
627
+ text: JSON.stringify({
628
+ spreadsheetId: res.data.spreadsheetId,
629
+ title: res.data.properties?.title,
630
+ sheets: sheetList
631
+ })
632
+ }
633
+ ]
634
+ };
635
+ }
636
+ );
637
+ registerTool(
638
+ "read_sheet_values",
639
+ "Read cell values from a Google Sheets range",
640
+ {
641
+ spreadsheetId: z2.string().describe("Google Sheets spreadsheet ID"),
642
+ range: z2.string().describe("A1 notation range (e.g. Sheet1!A1:C10)")
643
+ },
644
+ async (args) => {
645
+ const auth = await getAuthClient();
646
+ const sheets = google.sheets({ version: "v4", auth });
647
+ const res = await sheets.spreadsheets.values.get({
648
+ spreadsheetId: args.spreadsheetId,
649
+ range: args.range
650
+ });
651
+ return {
652
+ content: [
653
+ {
654
+ type: "text",
655
+ text: JSON.stringify({
656
+ range: res.data.range,
657
+ values: res.data.values || []
658
+ })
659
+ }
660
+ ]
661
+ };
662
+ }
663
+ );
664
+ registerTool(
665
+ "update_sheet_values",
666
+ "Update cell values in a Google Sheets range",
667
+ {
668
+ spreadsheetId: z2.string().describe("Google Sheets spreadsheet ID"),
669
+ range: z2.string().describe("A1 notation range (e.g. Sheet1!A1:B2)"),
670
+ values: z2.array(z2.array(z2.string())).describe("2D array of cell values")
671
+ },
672
+ async (args) => {
673
+ const auth = await getAuthClient();
674
+ const sheets = google.sheets({ version: "v4", auth });
675
+ const res = await sheets.spreadsheets.values.update({
676
+ spreadsheetId: args.spreadsheetId,
677
+ range: args.range,
678
+ valueInputOption: "USER_ENTERED",
679
+ requestBody: { values: args.values }
680
+ });
681
+ return {
682
+ content: [
683
+ {
684
+ type: "text",
685
+ text: JSON.stringify({
686
+ updatedRange: res.data.updatedRange,
687
+ updatedRows: res.data.updatedRows,
688
+ updatedColumns: res.data.updatedColumns,
689
+ updatedCells: res.data.updatedCells
690
+ })
691
+ }
692
+ ]
693
+ };
694
+ }
695
+ );
696
+ currentGroup = "docs";
697
+ registerTool(
698
+ "batch_update_doc",
699
+ "Apply batch updates to a Google Doc (insert/delete text, find-replace, formatting, tables, etc.)",
700
+ {
701
+ documentId: z2.string().describe("Google Docs document ID"),
702
+ requests: z2.array(z2.record(z2.string(), z2.unknown())).describe(
703
+ "Array of Google Docs batchUpdate request objects (e.g. insertText, deleteContentRange, replaceAllText, updateParagraphStyle, insertTable)"
704
+ )
705
+ },
706
+ async (args) => {
707
+ const auth = await getAuthClient();
708
+ const docs = google.docs({ version: "v1", auth });
709
+ const res = await docs.documents.batchUpdate({
710
+ documentId: args.documentId,
711
+ requestBody: { requests: args.requests }
712
+ });
713
+ return {
714
+ content: [
715
+ {
716
+ type: "text",
717
+ text: JSON.stringify({
718
+ documentId: res.data.documentId,
719
+ replies: res.data.replies
720
+ })
721
+ }
722
+ ]
723
+ };
724
+ }
725
+ );
726
+ currentGroup = "sheets";
727
+ registerTool(
728
+ "create_spreadsheet",
729
+ "Create a new Google Sheets spreadsheet",
730
+ {
731
+ title: z2.string().describe("Spreadsheet title")
732
+ },
733
+ async (args) => {
734
+ const auth = await getAuthClient();
735
+ const sheets = google.sheets({ version: "v4", auth });
736
+ const res = await sheets.spreadsheets.create({
737
+ requestBody: { properties: { title: args.title } }
738
+ });
739
+ return {
740
+ content: [
741
+ {
742
+ type: "text",
743
+ text: JSON.stringify({
744
+ spreadsheetId: res.data.spreadsheetId,
745
+ title: res.data.properties?.title,
746
+ url: res.data.spreadsheetUrl
747
+ })
748
+ }
749
+ ]
750
+ };
751
+ }
752
+ );
753
+ registerTool(
754
+ "create_sheet",
755
+ "Add a new sheet (tab) to an existing spreadsheet",
756
+ {
757
+ spreadsheetId: z2.string().describe("Google Sheets spreadsheet ID"),
758
+ title: z2.string().describe("Name for the new sheet tab")
759
+ },
760
+ async (args) => {
761
+ const auth = await getAuthClient();
762
+ const sheets = google.sheets({ version: "v4", auth });
763
+ const res = await sheets.spreadsheets.batchUpdate({
764
+ spreadsheetId: args.spreadsheetId,
765
+ requestBody: {
766
+ requests: [{ addSheet: { properties: { title: args.title } } }]
767
+ }
768
+ });
769
+ return {
770
+ content: [
771
+ {
772
+ type: "text",
773
+ text: JSON.stringify({ replies: res.data.replies })
774
+ }
775
+ ]
776
+ };
777
+ }
778
+ );
779
+ registerTool(
780
+ "format_sheet_range",
781
+ "Apply formatting (bold, colors, etc.) to a range of cells in a spreadsheet",
782
+ {
783
+ spreadsheetId: z2.string().describe("Google Sheets spreadsheet ID"),
784
+ sheetId: z2.number().describe("Sheet (tab) ID (0-based, from get_spreadsheet)"),
785
+ startRowIndex: z2.number().describe("Start row index (0-based, inclusive)"),
786
+ endRowIndex: z2.number().describe("End row index (0-based, exclusive)"),
787
+ startColumnIndex: z2.number().describe("Start column index (0-based, inclusive)"),
788
+ endColumnIndex: z2.number().describe("End column index (0-based, exclusive)"),
789
+ bold: z2.boolean().optional().describe("Set text bold"),
790
+ italic: z2.boolean().optional().describe("Set text italic"),
791
+ fontSize: z2.number().optional().describe("Font size in points"),
792
+ backgroundColor: z2.object({
793
+ red: z2.number(),
794
+ green: z2.number(),
795
+ blue: z2.number(),
796
+ alpha: z2.number().optional()
797
+ }).optional().describe("Background color (RGBA, values 0-1)")
798
+ },
799
+ async (args) => {
800
+ const auth = await getAuthClient();
801
+ const sheets = google.sheets({ version: "v4", auth });
802
+ const cellFormat = {};
803
+ const fields = [];
804
+ if (args.bold !== void 0 || args.italic !== void 0 || args.fontSize !== void 0) {
805
+ cellFormat.textFormat = {};
806
+ if (args.bold !== void 0) {
807
+ cellFormat.textFormat.bold = args.bold;
808
+ fields.push("userEnteredFormat.textFormat.bold");
809
+ }
810
+ if (args.italic !== void 0) {
811
+ cellFormat.textFormat.italic = args.italic;
812
+ fields.push("userEnteredFormat.textFormat.italic");
813
+ }
814
+ if (args.fontSize !== void 0) {
815
+ cellFormat.textFormat.fontSize = args.fontSize;
816
+ fields.push("userEnteredFormat.textFormat.fontSize");
817
+ }
818
+ }
819
+ if (args.backgroundColor) {
820
+ cellFormat.backgroundColor = args.backgroundColor;
821
+ fields.push("userEnteredFormat.backgroundColor");
822
+ }
823
+ const res = await sheets.spreadsheets.batchUpdate({
824
+ spreadsheetId: args.spreadsheetId,
825
+ requestBody: {
826
+ requests: [
827
+ {
828
+ repeatCell: {
829
+ range: {
830
+ sheetId: args.sheetId,
831
+ startRowIndex: args.startRowIndex,
832
+ endRowIndex: args.endRowIndex,
833
+ startColumnIndex: args.startColumnIndex,
834
+ endColumnIndex: args.endColumnIndex
835
+ },
836
+ cell: { userEnteredFormat: cellFormat },
837
+ fields: fields.join(",")
838
+ }
839
+ }
840
+ ]
841
+ }
842
+ });
843
+ return {
844
+ content: [
845
+ {
846
+ type: "text",
847
+ text: JSON.stringify({ replies: res.data.replies })
848
+ }
849
+ ]
850
+ };
851
+ }
852
+ );
853
+ currentGroup = "drive";
854
+ registerTool(
855
+ "search_files",
856
+ "Search Google Drive files by name or query",
857
+ {
858
+ query: z2.string().describe("Search query (file name or Drive query syntax)"),
859
+ mimeType: z2.string().optional().describe("Filter by MIME type"),
860
+ maxResults: z2.number().optional().default(20).describe("Maximum results")
861
+ },
862
+ async (args) => {
863
+ const auth = await getAuthClient();
864
+ const drive = google.drive({ version: "v3", auth });
865
+ let q = `name contains '${args.query.replace(/'/g, "\\'")}'`;
866
+ if (args.mimeType) {
867
+ q += ` and mimeType='${args.mimeType}'`;
868
+ }
869
+ const res = await drive.files.list({
870
+ q,
871
+ pageSize: args.maxResults || 20,
872
+ fields: "files(id, name, mimeType, modifiedTime, size, owners, webViewLink)"
873
+ });
874
+ return {
875
+ content: [
876
+ {
877
+ type: "text",
878
+ text: JSON.stringify({ files: res.data.files || [] })
879
+ }
880
+ ]
881
+ };
882
+ }
883
+ );
884
+ registerTool(
885
+ "get_file_metadata",
886
+ "Get metadata for a specific Drive file by ID",
887
+ {
888
+ fileId: z2.string().describe("Google Drive file ID")
889
+ },
890
+ async (args) => {
891
+ const auth = await getAuthClient();
892
+ const drive = google.drive({ version: "v3", auth });
893
+ const res = await drive.files.get({
894
+ fileId: args.fileId,
895
+ fields: "id, name, mimeType, createdTime, modifiedTime, size, owners, webViewLink, description"
896
+ });
897
+ return {
898
+ content: [{ type: "text", text: JSON.stringify(res.data) }]
899
+ };
900
+ }
901
+ );
902
+ registerTool(
903
+ "get_file_content",
904
+ "Get the text content of a Google Drive file (exports Docs/Sheets/Slides as plain text)",
905
+ {
906
+ fileId: z2.string().describe("Google Drive file ID"),
907
+ mimeType: z2.string().optional().describe(
908
+ "MIME type of the file (e.g. application/vnd.google-apps.document). Used to determine export format."
909
+ )
910
+ },
911
+ async (args) => {
912
+ const auth = await getAuthClient();
913
+ const drive = google.drive({ version: "v3", auth });
914
+ const googleDocTypes = {
915
+ "application/vnd.google-apps.document": "text/plain",
916
+ "application/vnd.google-apps.spreadsheet": "text/csv",
917
+ "application/vnd.google-apps.presentation": "text/plain"
918
+ };
919
+ const exportMimeType = args.mimeType ? googleDocTypes[args.mimeType] : void 0;
920
+ if (exportMimeType) {
921
+ const res2 = await drive.files.export({
922
+ fileId: args.fileId,
923
+ mimeType: exportMimeType
924
+ });
925
+ return {
926
+ content: [
927
+ {
928
+ type: "text",
929
+ text: JSON.stringify({ content: res2.data, fileId: args.fileId })
930
+ }
931
+ ]
932
+ };
933
+ }
934
+ const res = await drive.files.get({
935
+ fileId: args.fileId,
936
+ alt: "media"
937
+ });
938
+ return {
939
+ content: [
940
+ {
941
+ type: "text",
942
+ text: JSON.stringify({ content: res.data, fileId: args.fileId })
943
+ }
944
+ ]
945
+ };
946
+ }
947
+ );
948
+ currentGroup = "drive";
949
+ registerTool(
950
+ "create_drive_file",
951
+ "Create a new file in Google Drive",
952
+ {
953
+ name: z2.string().describe("File name"),
954
+ mimeType: z2.string().optional().describe(
955
+ "MIME type (e.g. text/plain, application/vnd.google-apps.document for a Google Doc)"
956
+ ),
957
+ content: z2.string().optional().describe("Text content for the file"),
958
+ parentFolderId: z2.string().optional().describe("Parent folder ID")
959
+ },
960
+ async (args) => {
961
+ const auth = await getAuthClient();
962
+ const drive = google.drive({ version: "v3", auth });
963
+ const requestBody = { name: args.name };
964
+ if (args.mimeType) requestBody.mimeType = args.mimeType;
965
+ if (args.parentFolderId) requestBody.parents = [args.parentFolderId];
966
+ const params = {
967
+ requestBody,
968
+ fields: "id, name, mimeType, webViewLink"
969
+ };
970
+ if (args.content) {
971
+ params.media = {
972
+ mimeType: args.mimeType || "text/plain",
973
+ body: args.content
974
+ };
975
+ }
976
+ const res = await drive.files.create(params);
977
+ return {
978
+ content: [
979
+ {
980
+ type: "text",
981
+ text: JSON.stringify({
982
+ fileId: res.data.id,
983
+ name: res.data.name,
984
+ mimeType: res.data.mimeType,
985
+ webViewLink: res.data.webViewLink
986
+ })
987
+ }
988
+ ]
989
+ };
990
+ }
991
+ );
992
+ registerTool(
993
+ "move_file",
994
+ "Move a file to a different folder in Google Drive",
995
+ {
996
+ fileId: z2.string().describe("File ID to move"),
997
+ destinationFolderId: z2.string().describe("Destination folder ID")
998
+ },
999
+ async (args) => {
1000
+ const auth = await getAuthClient();
1001
+ const drive = google.drive({ version: "v3", auth });
1002
+ const file = await drive.files.get({
1003
+ fileId: args.fileId,
1004
+ fields: "parents"
1005
+ });
1006
+ const previousParents = (file.data.parents || []).join(",");
1007
+ const res = await drive.files.update({
1008
+ fileId: args.fileId,
1009
+ addParents: args.destinationFolderId,
1010
+ removeParents: previousParents,
1011
+ fields: "id, name, parents"
1012
+ });
1013
+ return {
1014
+ content: [
1015
+ {
1016
+ type: "text",
1017
+ text: JSON.stringify({
1018
+ fileId: res.data.id,
1019
+ name: res.data.name,
1020
+ moved: true,
1021
+ newParents: res.data.parents
1022
+ })
1023
+ }
1024
+ ]
1025
+ };
1026
+ }
1027
+ );
1028
+ registerTool(
1029
+ "copy_file",
1030
+ "Copy a file in Google Drive",
1031
+ {
1032
+ fileId: z2.string().describe("File ID to copy"),
1033
+ name: z2.string().optional().describe("Name for the copy"),
1034
+ parentFolderId: z2.string().optional().describe("Destination folder ID for the copy")
1035
+ },
1036
+ async (args) => {
1037
+ const auth = await getAuthClient();
1038
+ const drive = google.drive({ version: "v3", auth });
1039
+ const requestBody = {};
1040
+ if (args.name) requestBody.name = args.name;
1041
+ if (args.parentFolderId) requestBody.parents = [args.parentFolderId];
1042
+ const res = await drive.files.copy({
1043
+ fileId: args.fileId,
1044
+ requestBody,
1045
+ fields: "id, name, mimeType, webViewLink"
1046
+ });
1047
+ return {
1048
+ content: [
1049
+ {
1050
+ type: "text",
1051
+ text: JSON.stringify({
1052
+ fileId: res.data.id,
1053
+ name: res.data.name,
1054
+ mimeType: res.data.mimeType,
1055
+ webViewLink: res.data.webViewLink
1056
+ })
1057
+ }
1058
+ ]
1059
+ };
1060
+ }
1061
+ );
1062
+ return {
1063
+ server,
1064
+ getRegisteredTools() {
1065
+ return registeredTools;
1066
+ },
1067
+ async start() {
1068
+ const transport = new StdioServerTransport();
1069
+ await server.connect(transport);
1070
+ }
1071
+ };
1072
+ }
1073
+ var isMainModule = process.argv[1] && import.meta.url.endsWith(process.argv[1].replace(/\\/g, "/"));
1074
+ if (isMainModule || process.env.FORGE_MCP_START === "true") {
1075
+ const tokenPath = process.env.FORGE_TOKEN_PATH;
1076
+ const clientId = process.env.FORGE_CLIENT_ID;
1077
+ const clientSecret = process.env.FORGE_CLIENT_SECRET;
1078
+ if (!tokenPath || !clientId || !clientSecret) {
1079
+ console.error(
1080
+ "Required environment variables: FORGE_TOKEN_PATH, FORGE_CLIENT_ID, FORGE_CLIENT_SECRET"
1081
+ );
1082
+ process.exit(1);
1083
+ }
1084
+ const mcpServer = createGoogleWorkspaceMCPServer({ tokenPath, clientId, clientSecret });
1085
+ mcpServer.start().catch((err) => {
1086
+ console.error("Failed to start MCP server:", err);
1087
+ process.exit(1);
1088
+ });
1089
+ }
1090
+ export {
1091
+ createGoogleWorkspaceMCPServer
1092
+ };
1093
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../../shared/src/mcp-auth/forge-token-manager.ts"],"sourcesContent":["/**\n * @goforgeit/mcp-google-workspace — MCP server for Google Workspace\n *\n * Provides Gmail, Calendar, and Drive tools via stdio transport.\n * Reads Forge's OAuth tokens and auto-refreshes them.\n */\n\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { z } from 'zod';\nimport { google } from 'googleapis';\nimport { createForgeTokenManager } from '@forge/shared/mcp-auth';\n\nconst GOOGLE_TOKEN_URL = 'https://oauth2.googleapis.com/token';\n\nexport type ToolGroup = 'gmail' | 'calendar' | 'chat' | 'docs' | 'sheets' | 'drive';\n\nexport interface GoogleWorkspaceMCPOptions {\n tokenPath: string;\n clientId: string;\n clientSecret: string;\n enabledGroups?: ToolGroup[];\n}\n\ninterface ToolHandler {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n handler: (args: any) => Promise<{ content: Array<{ type: 'text'; text: string }> }>;\n}\n\ninterface GoogleWorkspaceMCPServer {\n server: McpServer;\n getRegisteredTools(): Record<string, ToolHandler>;\n start(): Promise<void>;\n}\n\nexport function createGoogleWorkspaceMCPServer(\n options: GoogleWorkspaceMCPOptions,\n): GoogleWorkspaceMCPServer {\n const tokenManager = createForgeTokenManager({\n tokenPath: options.tokenPath,\n clientId: options.clientId,\n clientSecret: options.clientSecret,\n tokenUrl: GOOGLE_TOKEN_URL,\n });\n\n const server = new McpServer({\n name: '@goforgeit/mcp-google-workspace',\n version: '0.1.0',\n });\n\n const registeredTools: Record<string, ToolHandler> = {};\n\n async function getAuthClient() {\n const token = await tokenManager.getAccessToken();\n const auth = new google.auth.OAuth2();\n auth.setCredentials({ access_token: token });\n return auth;\n }\n\n const enabledGroups = options.enabledGroups ? new Set(options.enabledGroups) : null;\n\n let currentGroup: ToolGroup = 'gmail';\n\n function registerTool(\n name: string,\n description: string,\n schema: Record<string, z.ZodType>,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n handler: (args: any) => Promise<{ content: Array<{ type: 'text'; text: string }> }>,\n ) {\n if (enabledGroups && !enabledGroups.has(currentGroup)) return;\n registeredTools[name] = { handler };\n server.tool(name, description, schema, handler);\n }\n\n // --- Gmail Tools ---\n currentGroup = 'gmail';\n\n registerTool(\n 'search_emails',\n 'Search Gmail messages by query (uses Gmail search syntax like is:unread, from:, subject:)',\n {\n query: z.string().describe('Gmail search query (e.g. \"is:unread\", \"from:user@example.com\")'),\n maxResults: z\n .number()\n .optional()\n .default(10)\n .describe('Maximum number of results (default 10)'),\n },\n async (args: { query: string; maxResults?: number }) => {\n const auth = await getAuthClient();\n const gmail = google.gmail({ version: 'v1', auth });\n\n const listRes = await gmail.users.messages.list({\n userId: 'me',\n q: args.query,\n maxResults: args.maxResults || 10,\n });\n\n const messages = listRes.data.messages || [];\n const detailed = await Promise.all(\n messages.map(async (msg) => {\n const detail = await gmail.users.messages.get({\n userId: 'me',\n id: msg.id!,\n format: 'metadata',\n metadataHeaders: ['Subject', 'From', 'Date'],\n });\n const headers = detail.data.payload?.headers || [];\n return {\n id: detail.data.id,\n subject: headers.find((h) => h.name === 'Subject')?.value,\n from: headers.find((h) => h.name === 'From')?.value,\n date: headers.find((h) => h.name === 'Date')?.value,\n snippet: detail.data.snippet,\n labelIds: detail.data.labelIds,\n };\n }),\n );\n\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({\n messages: detailed,\n resultSizeEstimate: listRes.data.resultSizeEstimate,\n }),\n },\n ],\n };\n },\n );\n\n registerTool(\n 'get_email',\n 'Get full email details by message ID',\n {\n messageId: z.string().describe('Gmail message ID'),\n },\n async (args: { messageId: string }) => {\n const auth = await getAuthClient();\n const gmail = google.gmail({ version: 'v1', auth });\n\n const detail = await gmail.users.messages.get({\n userId: 'me',\n id: args.messageId,\n format: 'full',\n });\n\n const headers = detail.data.payload?.headers || [];\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({\n id: detail.data.id,\n subject: headers.find((h) => h.name === 'Subject')?.value,\n from: headers.find((h) => h.name === 'From')?.value,\n to: headers.find((h) => h.name === 'To')?.value,\n date: headers.find((h) => h.name === 'Date')?.value,\n snippet: detail.data.snippet,\n labelIds: detail.data.labelIds,\n body: detail.data.payload?.body?.data,\n }),\n },\n ],\n };\n },\n );\n\n registerTool('list_labels', 'List all Gmail labels', {}, async () => {\n const auth = await getAuthClient();\n const gmail = google.gmail({ version: 'v1', auth });\n\n const res = await gmail.users.labels.list({ userId: 'me' });\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({ labels: res.data.labels || [] }),\n },\n ],\n };\n });\n\n // --- Calendar Tools ---\n currentGroup = 'calendar';\n\n registerTool(\n 'list_events',\n 'List calendar events in a time range',\n {\n timeMin: z.string().optional().describe('Start time (ISO 8601, e.g. 2026-02-28T00:00:00Z)'),\n timeMax: z.string().optional().describe('End time (ISO 8601)'),\n calendarId: z\n .string()\n .optional()\n .default('primary')\n .describe('Calendar ID (default: primary)'),\n maxResults: z.number().optional().default(25).describe('Maximum events to return'),\n },\n async (args: {\n timeMin?: string;\n timeMax?: string;\n calendarId?: string;\n maxResults?: number;\n }) => {\n const auth = await getAuthClient();\n const calendar = google.calendar({ version: 'v3', auth });\n\n const res = await calendar.events.list({\n calendarId: args.calendarId || 'primary',\n timeMin: args.timeMin,\n timeMax: args.timeMax,\n maxResults: args.maxResults || 25,\n singleEvents: true,\n orderBy: 'startTime',\n });\n\n const events = (res.data.items || []).map((event) => ({\n id: event.id,\n summary: event.summary,\n description: event.description,\n start: event.start,\n end: event.end,\n location: event.location,\n attendees: event.attendees?.map((a) => ({\n email: a.email,\n responseStatus: a.responseStatus,\n })),\n htmlLink: event.htmlLink,\n }));\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ events }) }],\n };\n },\n );\n\n registerTool(\n 'get_event',\n 'Get a specific calendar event by ID',\n {\n eventId: z.string().describe('Calendar event ID'),\n calendarId: z\n .string()\n .optional()\n .default('primary')\n .describe('Calendar ID (default: primary)'),\n },\n async (args: { eventId: string; calendarId?: string }) => {\n const auth = await getAuthClient();\n const calendar = google.calendar({ version: 'v3', auth });\n\n const res = await calendar.events.get({\n calendarId: args.calendarId || 'primary',\n eventId: args.eventId,\n });\n\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({\n id: res.data.id,\n summary: res.data.summary,\n description: res.data.description,\n start: res.data.start,\n end: res.data.end,\n location: res.data.location,\n attendees: res.data.attendees,\n htmlLink: res.data.htmlLink,\n }),\n },\n ],\n };\n },\n );\n\n registerTool('list_calendars', 'List all calendars the user has access to', {}, async () => {\n const auth = await getAuthClient();\n const calendar = google.calendar({ version: 'v3', auth });\n\n const res = await calendar.calendarList.list();\n const calendars = (res.data.items || []).map((cal) => ({\n id: cal.id,\n summary: cal.summary,\n primary: cal.primary,\n accessRole: cal.accessRole,\n backgroundColor: cal.backgroundColor,\n }));\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ calendars }) }],\n };\n });\n\n // --- Gmail Write Tools ---\n currentGroup = 'gmail';\n\n registerTool(\n 'create_draft',\n 'Create a Gmail draft email',\n {\n to: z.string().describe('Recipient email address'),\n subject: z.string().describe('Email subject'),\n body: z.string().describe('Email body (plain text)'),\n cc: z.string().optional().describe('CC email address'),\n bcc: z.string().optional().describe('BCC email address'),\n },\n async (args: { to: string; subject: string; body: string; cc?: string; bcc?: string }) => {\n const auth = await getAuthClient();\n const gmail = google.gmail({ version: 'v1', auth });\n\n const headers = [\n `To: ${args.to}`,\n `Subject: ${args.subject}`,\n 'Content-Type: text/plain; charset=utf-8',\n ];\n if (args.cc) headers.push(`Cc: ${args.cc}`);\n if (args.bcc) headers.push(`Bcc: ${args.bcc}`);\n const raw = Buffer.from(headers.join('\\r\\n') + '\\r\\n\\r\\n' + args.body).toString('base64url');\n\n const res = await gmail.users.drafts.create({\n userId: 'me',\n requestBody: { message: { raw } },\n });\n\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({ draftId: res.data.id, messageId: res.data.message?.id }),\n },\n ],\n };\n },\n );\n\n registerTool(\n 'archive_email',\n 'Archive a Gmail message (removes from Inbox)',\n {\n messageId: z.string().describe('Gmail message ID to archive'),\n },\n async (args: { messageId: string }) => {\n const auth = await getAuthClient();\n const gmail = google.gmail({ version: 'v1', auth });\n\n await gmail.users.messages.modify({\n userId: 'me',\n id: args.messageId,\n requestBody: { removeLabelIds: ['INBOX'] },\n });\n\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({ archived: true, messageId: args.messageId }),\n },\n ],\n };\n },\n );\n\n // --- Calendar Write Tools ---\n currentGroup = 'calendar';\n\n registerTool(\n 'create_event',\n 'Create a new Google Calendar event',\n {\n summary: z.string().describe('Event title'),\n startDateTime: z.string().describe('Start time (ISO 8601, e.g. 2026-03-01T10:00:00-07:00)'),\n endDateTime: z.string().describe('End time (ISO 8601)'),\n description: z.string().optional().describe('Event description'),\n location: z.string().optional().describe('Event location'),\n attendeeEmails: z.array(z.string()).optional().describe('Attendee email addresses'),\n calendarId: z\n .string()\n .optional()\n .default('primary')\n .describe('Calendar ID (default: primary)'),\n },\n async (args: {\n summary: string;\n startDateTime: string;\n endDateTime: string;\n description?: string;\n location?: string;\n attendeeEmails?: string[];\n calendarId?: string;\n }) => {\n const auth = await getAuthClient();\n const calendar = google.calendar({ version: 'v3', auth });\n\n const res = await calendar.events.insert({\n calendarId: args.calendarId || 'primary',\n requestBody: {\n summary: args.summary,\n description: args.description,\n location: args.location,\n start: { dateTime: args.startDateTime },\n end: { dateTime: args.endDateTime },\n attendees: args.attendeeEmails?.map((email) => ({ email })),\n },\n });\n\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({\n eventId: res.data.id,\n htmlLink: res.data.htmlLink,\n summary: res.data.summary,\n }),\n },\n ],\n };\n },\n );\n\n registerTool(\n 'update_event',\n 'Update an existing Google Calendar event',\n {\n eventId: z.string().describe('Calendar event ID'),\n summary: z.string().optional().describe('New event title'),\n startDateTime: z.string().optional().describe('New start time (ISO 8601)'),\n endDateTime: z.string().optional().describe('New end time (ISO 8601)'),\n description: z.string().optional().describe('New event description'),\n location: z.string().optional().describe('New event location'),\n calendarId: z\n .string()\n .optional()\n .default('primary')\n .describe('Calendar ID (default: primary)'),\n },\n async (args: {\n eventId: string;\n summary?: string;\n startDateTime?: string;\n endDateTime?: string;\n description?: string;\n location?: string;\n calendarId?: string;\n }) => {\n const auth = await getAuthClient();\n const calendar = google.calendar({ version: 'v3', auth });\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const patch: Record<string, any> = {};\n if (args.summary) patch.summary = args.summary;\n if (args.description !== undefined) patch.description = args.description;\n if (args.location !== undefined) patch.location = args.location;\n if (args.startDateTime) patch.start = { dateTime: args.startDateTime };\n if (args.endDateTime) patch.end = { dateTime: args.endDateTime };\n\n const res = await calendar.events.patch({\n calendarId: args.calendarId || 'primary',\n eventId: args.eventId,\n requestBody: patch,\n });\n\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({\n eventId: res.data.id,\n summary: res.data.summary,\n updated: true,\n }),\n },\n ],\n };\n },\n );\n\n registerTool(\n 'delete_event',\n 'Delete a Google Calendar event',\n {\n eventId: z.string().describe('Calendar event ID to delete'),\n calendarId: z\n .string()\n .optional()\n .default('primary')\n .describe('Calendar ID (default: primary)'),\n },\n async (args: { eventId: string; calendarId?: string }) => {\n const auth = await getAuthClient();\n const calendar = google.calendar({ version: 'v3', auth });\n\n await calendar.events.delete({\n calendarId: args.calendarId || 'primary',\n eventId: args.eventId,\n });\n\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({ deleted: true, eventId: args.eventId }),\n },\n ],\n };\n },\n );\n\n // --- Google Chat Tools ---\n currentGroup = 'chat';\n\n registerTool('list_spaces', 'List Google Chat spaces (rooms, DMs, group chats)', {}, async () => {\n const auth = await getAuthClient();\n const chat = google.chat({ version: 'v1', auth });\n\n const res = await chat.spaces.list();\n const spaces = (res.data.spaces || []).map((space) => ({\n name: space.name,\n displayName: space.displayName,\n type: space.type,\n singleUserBotDm: space.singleUserBotDm,\n }));\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ spaces }) }],\n };\n });\n\n registerTool(\n 'get_space_messages',\n 'Get recent messages from a Google Chat space',\n {\n spaceName: z.string().describe('Space resource name (e.g. spaces/abc123)'),\n maxResults: z.number().optional().default(25).describe('Maximum messages to return'),\n },\n async (args: { spaceName: string; maxResults?: number }) => {\n const auth = await getAuthClient();\n const chat = google.chat({ version: 'v1', auth });\n\n const res = await chat.spaces.messages.list({\n parent: args.spaceName,\n pageSize: args.maxResults || 25,\n });\n\n const messages = (res.data.messages || []).map((msg) => ({\n name: msg.name,\n sender: msg.sender,\n text: msg.text,\n createTime: msg.createTime,\n }));\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ messages }) }],\n };\n },\n );\n\n registerTool(\n 'send_chat_message',\n 'Send a message to a Google Chat space',\n {\n spaceName: z.string().describe('Space resource name (e.g. spaces/abc123)'),\n text: z.string().describe('Message text to send'),\n },\n async (args: { spaceName: string; text: string }) => {\n const auth = await getAuthClient();\n const chat = google.chat({ version: 'v1', auth });\n\n const res = await chat.spaces.messages.create({\n parent: args.spaceName,\n requestBody: { text: args.text },\n });\n\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({\n messageName: res.data.name,\n text: res.data.text,\n createTime: res.data.createTime,\n }),\n },\n ],\n };\n },\n );\n\n // --- Google Docs Tools ---\n currentGroup = 'docs';\n\n registerTool(\n 'get_document',\n 'Get a Google Doc by document ID (returns structured content)',\n {\n documentId: z.string().describe('Google Docs document ID'),\n },\n async (args: { documentId: string }) => {\n const auth = await getAuthClient();\n const docs = google.docs({ version: 'v1', auth });\n\n const res = await docs.documents.get({ documentId: args.documentId });\n\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({\n documentId: res.data.documentId,\n title: res.data.title,\n body: res.data.body,\n }),\n },\n ],\n };\n },\n );\n\n registerTool(\n 'create_document',\n 'Create a new Google Doc',\n {\n title: z.string().describe('Document title'),\n },\n async (args: { title: string }) => {\n const auth = await getAuthClient();\n const docs = google.docs({ version: 'v1', auth });\n\n const res = await docs.documents.create({\n requestBody: { title: args.title },\n });\n\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({\n documentId: res.data.documentId,\n title: res.data.title,\n }),\n },\n ],\n };\n },\n );\n\n // --- Google Sheets Tools ---\n currentGroup = 'sheets';\n\n registerTool(\n 'get_spreadsheet',\n 'Get Google Sheets spreadsheet metadata and sheet names',\n {\n spreadsheetId: z.string().describe('Google Sheets spreadsheet ID'),\n },\n async (args: { spreadsheetId: string }) => {\n const auth = await getAuthClient();\n const sheets = google.sheets({ version: 'v4', auth });\n\n const res = await sheets.spreadsheets.get({\n spreadsheetId: args.spreadsheetId,\n });\n\n const sheetList = (res.data.sheets || []).map((s) => ({\n sheetId: s.properties?.sheetId,\n title: s.properties?.title,\n index: s.properties?.index,\n }));\n\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({\n spreadsheetId: res.data.spreadsheetId,\n title: res.data.properties?.title,\n sheets: sheetList,\n }),\n },\n ],\n };\n },\n );\n\n registerTool(\n 'read_sheet_values',\n 'Read cell values from a Google Sheets range',\n {\n spreadsheetId: z.string().describe('Google Sheets spreadsheet ID'),\n range: z.string().describe('A1 notation range (e.g. Sheet1!A1:C10)'),\n },\n async (args: { spreadsheetId: string; range: string }) => {\n const auth = await getAuthClient();\n const sheets = google.sheets({ version: 'v4', auth });\n\n const res = await sheets.spreadsheets.values.get({\n spreadsheetId: args.spreadsheetId,\n range: args.range,\n });\n\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({\n range: res.data.range,\n values: res.data.values || [],\n }),\n },\n ],\n };\n },\n );\n\n registerTool(\n 'update_sheet_values',\n 'Update cell values in a Google Sheets range',\n {\n spreadsheetId: z.string().describe('Google Sheets spreadsheet ID'),\n range: z.string().describe('A1 notation range (e.g. Sheet1!A1:B2)'),\n values: z.array(z.array(z.string())).describe('2D array of cell values'),\n },\n async (args: { spreadsheetId: string; range: string; values: string[][] }) => {\n const auth = await getAuthClient();\n const sheets = google.sheets({ version: 'v4', auth });\n\n const res = await sheets.spreadsheets.values.update({\n spreadsheetId: args.spreadsheetId,\n range: args.range,\n valueInputOption: 'USER_ENTERED',\n requestBody: { values: args.values },\n });\n\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({\n updatedRange: res.data.updatedRange,\n updatedRows: res.data.updatedRows,\n updatedColumns: res.data.updatedColumns,\n updatedCells: res.data.updatedCells,\n }),\n },\n ],\n };\n },\n );\n\n // --- Google Docs Write Tools ---\n currentGroup = 'docs';\n\n registerTool(\n 'batch_update_doc',\n 'Apply batch updates to a Google Doc (insert/delete text, find-replace, formatting, tables, etc.)',\n {\n documentId: z.string().describe('Google Docs document ID'),\n requests: z\n .array(z.record(z.string(), z.unknown()))\n .describe(\n 'Array of Google Docs batchUpdate request objects (e.g. insertText, deleteContentRange, replaceAllText, updateParagraphStyle, insertTable)',\n ),\n },\n async (args: { documentId: string; requests: Record<string, unknown>[] }) => {\n const auth = await getAuthClient();\n const docs = google.docs({ version: 'v1', auth });\n\n const res = await docs.documents.batchUpdate({\n documentId: args.documentId,\n requestBody: { requests: args.requests },\n });\n\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({\n documentId: res.data.documentId,\n replies: res.data.replies,\n }),\n },\n ],\n };\n },\n );\n\n // --- Google Sheets Write Tools ---\n currentGroup = 'sheets';\n\n registerTool(\n 'create_spreadsheet',\n 'Create a new Google Sheets spreadsheet',\n {\n title: z.string().describe('Spreadsheet title'),\n },\n async (args: { title: string }) => {\n const auth = await getAuthClient();\n const sheets = google.sheets({ version: 'v4', auth });\n\n const res = await sheets.spreadsheets.create({\n requestBody: { properties: { title: args.title } },\n });\n\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({\n spreadsheetId: res.data.spreadsheetId,\n title: res.data.properties?.title,\n url: res.data.spreadsheetUrl,\n }),\n },\n ],\n };\n },\n );\n\n registerTool(\n 'create_sheet',\n 'Add a new sheet (tab) to an existing spreadsheet',\n {\n spreadsheetId: z.string().describe('Google Sheets spreadsheet ID'),\n title: z.string().describe('Name for the new sheet tab'),\n },\n async (args: { spreadsheetId: string; title: string }) => {\n const auth = await getAuthClient();\n const sheets = google.sheets({ version: 'v4', auth });\n\n const res = await sheets.spreadsheets.batchUpdate({\n spreadsheetId: args.spreadsheetId,\n requestBody: {\n requests: [{ addSheet: { properties: { title: args.title } } }],\n },\n });\n\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({ replies: res.data.replies }),\n },\n ],\n };\n },\n );\n\n registerTool(\n 'format_sheet_range',\n 'Apply formatting (bold, colors, etc.) to a range of cells in a spreadsheet',\n {\n spreadsheetId: z.string().describe('Google Sheets spreadsheet ID'),\n sheetId: z.number().describe('Sheet (tab) ID (0-based, from get_spreadsheet)'),\n startRowIndex: z.number().describe('Start row index (0-based, inclusive)'),\n endRowIndex: z.number().describe('End row index (0-based, exclusive)'),\n startColumnIndex: z.number().describe('Start column index (0-based, inclusive)'),\n endColumnIndex: z.number().describe('End column index (0-based, exclusive)'),\n bold: z.boolean().optional().describe('Set text bold'),\n italic: z.boolean().optional().describe('Set text italic'),\n fontSize: z.number().optional().describe('Font size in points'),\n backgroundColor: z\n .object({\n red: z.number(),\n green: z.number(),\n blue: z.number(),\n alpha: z.number().optional(),\n })\n .optional()\n .describe('Background color (RGBA, values 0-1)'),\n },\n async (args: {\n spreadsheetId: string;\n sheetId: number;\n startRowIndex: number;\n endRowIndex: number;\n startColumnIndex: number;\n endColumnIndex: number;\n bold?: boolean;\n italic?: boolean;\n fontSize?: number;\n backgroundColor?: { red: number; green: number; blue: number; alpha?: number };\n }) => {\n const auth = await getAuthClient();\n const sheets = google.sheets({ version: 'v4', auth });\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const cellFormat: Record<string, any> = {};\n const fields: string[] = [];\n\n if (args.bold !== undefined || args.italic !== undefined || args.fontSize !== undefined) {\n cellFormat.textFormat = {};\n if (args.bold !== undefined) {\n cellFormat.textFormat.bold = args.bold;\n fields.push('userEnteredFormat.textFormat.bold');\n }\n if (args.italic !== undefined) {\n cellFormat.textFormat.italic = args.italic;\n fields.push('userEnteredFormat.textFormat.italic');\n }\n if (args.fontSize !== undefined) {\n cellFormat.textFormat.fontSize = args.fontSize;\n fields.push('userEnteredFormat.textFormat.fontSize');\n }\n }\n if (args.backgroundColor) {\n cellFormat.backgroundColor = args.backgroundColor;\n fields.push('userEnteredFormat.backgroundColor');\n }\n\n const res = await sheets.spreadsheets.batchUpdate({\n spreadsheetId: args.spreadsheetId,\n requestBody: {\n requests: [\n {\n repeatCell: {\n range: {\n sheetId: args.sheetId,\n startRowIndex: args.startRowIndex,\n endRowIndex: args.endRowIndex,\n startColumnIndex: args.startColumnIndex,\n endColumnIndex: args.endColumnIndex,\n },\n cell: { userEnteredFormat: cellFormat },\n fields: fields.join(','),\n },\n },\n ],\n },\n });\n\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({ replies: res.data.replies }),\n },\n ],\n };\n },\n );\n\n // --- Drive Tools ---\n currentGroup = 'drive';\n\n registerTool(\n 'search_files',\n 'Search Google Drive files by name or query',\n {\n query: z.string().describe('Search query (file name or Drive query syntax)'),\n mimeType: z.string().optional().describe('Filter by MIME type'),\n maxResults: z.number().optional().default(20).describe('Maximum results'),\n },\n async (args: { query: string; mimeType?: string; maxResults?: number }) => {\n const auth = await getAuthClient();\n const drive = google.drive({ version: 'v3', auth });\n\n let q = `name contains '${args.query.replace(/'/g, \"\\\\'\")}'`;\n if (args.mimeType) {\n q += ` and mimeType='${args.mimeType}'`;\n }\n\n const res = await drive.files.list({\n q,\n pageSize: args.maxResults || 20,\n fields: 'files(id, name, mimeType, modifiedTime, size, owners, webViewLink)',\n });\n\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({ files: res.data.files || [] }),\n },\n ],\n };\n },\n );\n\n registerTool(\n 'get_file_metadata',\n 'Get metadata for a specific Drive file by ID',\n {\n fileId: z.string().describe('Google Drive file ID'),\n },\n async (args: { fileId: string }) => {\n const auth = await getAuthClient();\n const drive = google.drive({ version: 'v3', auth });\n\n const res = await drive.files.get({\n fileId: args.fileId,\n fields:\n 'id, name, mimeType, createdTime, modifiedTime, size, owners, webViewLink, description',\n });\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(res.data) }],\n };\n },\n );\n\n registerTool(\n 'get_file_content',\n 'Get the text content of a Google Drive file (exports Docs/Sheets/Slides as plain text)',\n {\n fileId: z.string().describe('Google Drive file ID'),\n mimeType: z\n .string()\n .optional()\n .describe(\n 'MIME type of the file (e.g. application/vnd.google-apps.document). Used to determine export format.',\n ),\n },\n async (args: { fileId: string; mimeType?: string }) => {\n const auth = await getAuthClient();\n const drive = google.drive({ version: 'v3', auth });\n\n // Google Workspace native files need export; other files use direct download\n const googleDocTypes: Record<string, string> = {\n 'application/vnd.google-apps.document': 'text/plain',\n 'application/vnd.google-apps.spreadsheet': 'text/csv',\n 'application/vnd.google-apps.presentation': 'text/plain',\n };\n\n const exportMimeType = args.mimeType ? googleDocTypes[args.mimeType] : undefined;\n\n if (exportMimeType) {\n const res = await drive.files.export({\n fileId: args.fileId,\n mimeType: exportMimeType,\n });\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({ content: res.data, fileId: args.fileId }),\n },\n ],\n };\n }\n\n // For non-Google files, get as media\n const res = await drive.files.get({\n fileId: args.fileId,\n alt: 'media',\n });\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({ content: res.data, fileId: args.fileId }),\n },\n ],\n };\n },\n );\n\n // --- Drive Write Tools ---\n currentGroup = 'drive';\n\n registerTool(\n 'create_drive_file',\n 'Create a new file in Google Drive',\n {\n name: z.string().describe('File name'),\n mimeType: z\n .string()\n .optional()\n .describe(\n 'MIME type (e.g. text/plain, application/vnd.google-apps.document for a Google Doc)',\n ),\n content: z.string().optional().describe('Text content for the file'),\n parentFolderId: z.string().optional().describe('Parent folder ID'),\n },\n async (args: {\n name: string;\n mimeType?: string;\n content?: string;\n parentFolderId?: string;\n }) => {\n const auth = await getAuthClient();\n const drive = google.drive({ version: 'v3', auth });\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const requestBody: Record<string, any> = { name: args.name };\n if (args.mimeType) requestBody.mimeType = args.mimeType;\n if (args.parentFolderId) requestBody.parents = [args.parentFolderId];\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const params: Record<string, any> = {\n requestBody,\n fields: 'id, name, mimeType, webViewLink',\n };\n\n if (args.content) {\n params.media = {\n mimeType: args.mimeType || 'text/plain',\n body: args.content,\n };\n }\n\n const res = await drive.files.create(params);\n\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({\n fileId: res.data.id,\n name: res.data.name,\n mimeType: res.data.mimeType,\n webViewLink: res.data.webViewLink,\n }),\n },\n ],\n };\n },\n );\n\n registerTool(\n 'move_file',\n 'Move a file to a different folder in Google Drive',\n {\n fileId: z.string().describe('File ID to move'),\n destinationFolderId: z.string().describe('Destination folder ID'),\n },\n async (args: { fileId: string; destinationFolderId: string }) => {\n const auth = await getAuthClient();\n const drive = google.drive({ version: 'v3', auth });\n\n // Get current parents\n const file = await drive.files.get({\n fileId: args.fileId,\n fields: 'parents',\n });\n const previousParents = (file.data.parents || []).join(',');\n\n const res = await drive.files.update({\n fileId: args.fileId,\n addParents: args.destinationFolderId,\n removeParents: previousParents,\n fields: 'id, name, parents',\n });\n\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({\n fileId: res.data.id,\n name: res.data.name,\n moved: true,\n newParents: res.data.parents,\n }),\n },\n ],\n };\n },\n );\n\n registerTool(\n 'copy_file',\n 'Copy a file in Google Drive',\n {\n fileId: z.string().describe('File ID to copy'),\n name: z.string().optional().describe('Name for the copy'),\n parentFolderId: z.string().optional().describe('Destination folder ID for the copy'),\n },\n async (args: { fileId: string; name?: string; parentFolderId?: string }) => {\n const auth = await getAuthClient();\n const drive = google.drive({ version: 'v3', auth });\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const requestBody: Record<string, any> = {};\n if (args.name) requestBody.name = args.name;\n if (args.parentFolderId) requestBody.parents = [args.parentFolderId];\n\n const res = await drive.files.copy({\n fileId: args.fileId,\n requestBody,\n fields: 'id, name, mimeType, webViewLink',\n });\n\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({\n fileId: res.data.id,\n name: res.data.name,\n mimeType: res.data.mimeType,\n webViewLink: res.data.webViewLink,\n }),\n },\n ],\n };\n },\n );\n\n return {\n server,\n getRegisteredTools() {\n return registeredTools;\n },\n async start() {\n const transport = new StdioServerTransport();\n await server.connect(transport);\n },\n };\n}\n\n// CLI entry point\nconst isMainModule =\n process.argv[1] && import.meta.url.endsWith(process.argv[1].replace(/\\\\/g, '/'));\nif (isMainModule || process.env.FORGE_MCP_START === 'true') {\n const tokenPath = process.env.FORGE_TOKEN_PATH;\n const clientId = process.env.FORGE_CLIENT_ID;\n const clientSecret = process.env.FORGE_CLIENT_SECRET;\n\n if (!tokenPath || !clientId || !clientSecret) {\n console.error(\n 'Required environment variables: FORGE_TOKEN_PATH, FORGE_CLIENT_ID, FORGE_CLIENT_SECRET',\n );\n process.exit(1);\n }\n\n const mcpServer = createGoogleWorkspaceMCPServer({ tokenPath, clientId, clientSecret });\n mcpServer.start().catch((err) => {\n console.error('Failed to start MCP server:', err);\n process.exit(1);\n });\n}\n","/**\n * Forge Token Manager — reads Forge's OAuthTokens format and handles refresh.\n *\n * Used by @forge/mcp-* packages to get valid access tokens for API calls.\n * Reads from FORGE_TOKEN_PATH, validates with Zod, and refreshes when\n * the token is expired or within 5 minutes of expiry.\n */\n\nimport * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\nimport { z } from 'zod';\n\n/** Zod schema for Forge's OAuth token file format */\nexport const ForgeOAuthTokensSchema = z.object({\n accessToken: z.string(),\n refreshToken: z.string().optional(),\n expiresAt: z.number(),\n providerId: z.string(),\n connectedAt: z.string(),\n scope: z.string().optional(),\n email: z.string().optional(),\n accountId: z.string().optional(),\n});\n\nexport type ForgeOAuthTokens = z.infer<typeof ForgeOAuthTokensSchema>;\n\nexport interface ForgeTokenManagerOptions {\n /** Path to the Forge OAuth token JSON file */\n tokenPath: string;\n /** OAuth client ID for token refresh */\n clientId: string;\n /** OAuth client secret for token refresh */\n clientSecret: string;\n /** OAuth token endpoint URL for refresh requests */\n tokenUrl: string;\n /** Buffer in ms before expiry to trigger refresh (default: 5 minutes) */\n refreshBufferMs?: number;\n}\n\nexport interface ForgeTokenManager {\n /** Returns a valid access token, refreshing if needed */\n getAccessToken(): Promise<string>;\n /** Returns the current token data, or null if not yet loaded */\n getTokenInfo(): ForgeOAuthTokens | null;\n /** Register a callback for when tokens are refreshed */\n onRefresh(callback: (tokens: ForgeOAuthTokens) => void): void;\n}\n\nconst DEFAULT_REFRESH_BUFFER_MS = 5 * 60 * 1000; // 5 minutes\n\nexport function createForgeTokenManager(options: ForgeTokenManagerOptions): ForgeTokenManager {\n const {\n tokenPath,\n clientId,\n clientSecret,\n tokenUrl,\n refreshBufferMs = DEFAULT_REFRESH_BUFFER_MS,\n } = options;\n\n let cachedTokens: ForgeOAuthTokens | null = null;\n const refreshCallbacks: Array<(tokens: ForgeOAuthTokens) => void> = [];\n\n function isExpiredOrNearExpiry(tokens: ForgeOAuthTokens): boolean {\n return Date.now() >= tokens.expiresAt - refreshBufferMs;\n }\n\n async function readTokenFile(): Promise<ForgeOAuthTokens> {\n let raw: string;\n try {\n raw = await fs.readFile(tokenPath, 'utf-8');\n } catch (err: unknown) {\n if (err && typeof err === 'object' && 'code' in err && err.code === 'ENOENT') {\n throw new Error(`Token file not found: ${tokenPath}`);\n }\n throw err;\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n throw new Error(`Invalid JSON in token file: ${tokenPath}`);\n }\n\n const result = ForgeOAuthTokensSchema.safeParse(parsed);\n if (!result.success) {\n throw new Error(`Invalid token file format: ${result.error.message}`);\n }\n\n return result.data;\n }\n\n async function refreshToken(tokens: ForgeOAuthTokens): Promise<ForgeOAuthTokens> {\n if (!tokens.refreshToken) {\n throw new Error('No refresh token available — cannot refresh expired token');\n }\n\n const body = new URLSearchParams({\n grant_type: 'refresh_token',\n refresh_token: tokens.refreshToken,\n client_id: clientId,\n client_secret: clientSecret,\n });\n\n const response = await fetch(tokenUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: body.toString(),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Token refresh failed (${response.status}): ${errorText}`);\n }\n\n const data = await response.json();\n\n const refreshedTokens: ForgeOAuthTokens = {\n ...tokens,\n accessToken: data.access_token,\n refreshToken: data.refresh_token || tokens.refreshToken,\n expiresAt: Date.now() + data.expires_in * 1000,\n };\n\n // Write refreshed tokens back to file\n await fs.mkdir(path.dirname(tokenPath), { recursive: true });\n await fs.writeFile(tokenPath, JSON.stringify(refreshedTokens), { mode: 0o600 });\n\n // Notify listeners\n for (const cb of refreshCallbacks) {\n cb(refreshedTokens);\n }\n\n return refreshedTokens;\n }\n\n return {\n async getAccessToken(): Promise<string> {\n if (cachedTokens && !isExpiredOrNearExpiry(cachedTokens)) {\n return cachedTokens.accessToken;\n }\n\n const tokens = cachedTokens || (await readTokenFile());\n\n if (isExpiredOrNearExpiry(tokens)) {\n if (tokens.refreshToken) {\n cachedTokens = await refreshToken(tokens);\n } else {\n // No refresh token — return the access token as-is.\n // Some providers (e.g. Slack bot tokens) issue non-expiring tokens\n // without refresh tokens, so the expiresAt may be unreliable.\n cachedTokens = tokens;\n }\n } else {\n cachedTokens = tokens;\n }\n\n return cachedTokens.accessToken;\n },\n\n getTokenInfo(): ForgeOAuthTokens | null {\n return cachedTokens;\n },\n\n onRefresh(callback: (tokens: ForgeOAuthTokens) => void): void {\n refreshCallbacks.push(callback);\n },\n };\n}\n\n/**\n * Create a ForgeTokenManager from standard environment variables.\n *\n * Expected env vars:\n * - FORGE_TOKEN_PATH: path to the OAuth token JSON file\n * - FORGE_CLIENT_ID: OAuth client ID\n * - FORGE_CLIENT_SECRET: OAuth client secret\n *\n * @param tokenUrl The provider's token endpoint URL\n */\nexport function createForgeTokenManagerFromEnv(tokenUrl: string): ForgeTokenManager {\n const tokenPath = process.env.FORGE_TOKEN_PATH;\n const clientId = process.env.FORGE_CLIENT_ID;\n const clientSecret = process.env.FORGE_CLIENT_SECRET;\n\n if (!tokenPath) throw new Error('FORGE_TOKEN_PATH environment variable is required');\n if (!clientId) throw new Error('FORGE_CLIENT_ID environment variable is required');\n if (!clientSecret) throw new Error('FORGE_CLIENT_SECRET environment variable is required');\n\n return createForgeTokenManager({ tokenPath, clientId, clientSecret, tokenUrl });\n}\n"],"mappings":"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","names":["z","z","res"]}
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@goforgeit/mcp-google-workspace",
3
+ "version": "0.4.11",
4
+ "type": "module",
5
+ "description": "Forge MCP server for Google Workspace (Gmail, Calendar, Drive)",
6
+ "bin": {
7
+ "mcp-google-workspace": "./dist/index.js"
8
+ },
9
+ "main": "dist/index.js",
10
+ "types": "dist/index.d.ts",
11
+ "publishConfig": {
12
+ "access": "public"
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "README.md"
17
+ ],
18
+ "scripts": {
19
+ "build": "tsup",
20
+ "typecheck": "tsc --noEmit",
21
+ "test": "vitest run"
22
+ },
23
+ "dependencies": {
24
+ "@modelcontextprotocol/sdk": "^1.12.1",
25
+ "googleapis": "^148.0.0",
26
+ "zod": "^4.2.1"
27
+ },
28
+ "devDependencies": {
29
+ "@forge/shared": "workspace:*",
30
+ "@types/node": "^22.10.2",
31
+ "tsup": "^8.0.0",
32
+ "vitest": "^3.2.4"
33
+ }
34
+ }