@alanse/mcp-server-google-workspace 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/README.md +146 -17
  2. package/dist/auth.js +5 -0
  3. package/dist/lib/calendar-helpers.js +197 -0
  4. package/dist/lib/drive-helpers.js +263 -0
  5. package/dist/lib/gmail-helpers.js +204 -0
  6. package/dist/tools/calendar/acl/calendar_acl_insert.js +80 -0
  7. package/dist/tools/calendar/acl/calendar_acl_list.js +82 -0
  8. package/dist/tools/calendar/basic/calendar_create_event.js +113 -0
  9. package/dist/tools/calendar/basic/calendar_delete_event.js +52 -0
  10. package/dist/tools/calendar/basic/calendar_get_event.js +52 -0
  11. package/dist/tools/calendar/basic/calendar_list_events.js +86 -0
  12. package/dist/tools/calendar/basic/calendar_update_event.js +116 -0
  13. package/dist/tools/calendar/calendarlist/calendar_calendarlist_get.js +73 -0
  14. package/dist/tools/calendar/calendarlist/calendar_calendarlist_list.js +87 -0
  15. package/dist/tools/calendar/calendars/calendar_calendars_get.js +52 -0
  16. package/dist/tools/calendar/calendars/calendar_calendars_insert.js +66 -0
  17. package/dist/tools/calendar/calendars/calendar_calendars_update.js +85 -0
  18. package/dist/tools/calendar/colors/calendar_colors_get.js +46 -0
  19. package/dist/tools/calendar/events_advanced/calendar_events_instances.js +81 -0
  20. package/dist/tools/calendar/events_advanced/calendar_events_move.js +63 -0
  21. package/dist/tools/calendar/events_advanced/calendar_events_quickadd.js +52 -0
  22. package/dist/tools/calendar/freebusy/calendar_freebusy_query.js +69 -0
  23. package/dist/tools/calendar/settings/calendar_settings_list.js +81 -0
  24. package/dist/tools/drive/advanced/drive_empty_trash.js +56 -0
  25. package/dist/tools/drive/advanced/drive_export_file.js +158 -0
  26. package/dist/tools/drive/advanced/drive_list_revisions.js +80 -0
  27. package/dist/tools/drive/basic/drive_get_metadata.js +49 -0
  28. package/dist/tools/drive/basic/drive_list_files.js +76 -0
  29. package/dist/tools/drive/file/drive_copy_file.js +79 -0
  30. package/dist/tools/drive/file/drive_create_file.js +72 -0
  31. package/dist/tools/drive/file/drive_delete_file.js +48 -0
  32. package/dist/tools/drive/file/drive_move_file.js +79 -0
  33. package/dist/tools/drive/file/drive_rename_file.js +58 -0
  34. package/dist/tools/drive/file/drive_update_file.js +106 -0
  35. package/dist/tools/drive/file/drive_upload_file.js +80 -0
  36. package/dist/tools/drive/folder/drive_create_folder.js +67 -0
  37. package/dist/tools/drive/folder/drive_list_folder_contents.js +68 -0
  38. package/dist/tools/drive/folder/drive_move_to_folder.js +59 -0
  39. package/dist/tools/drive/permissions/drive_list_permissions.js +115 -0
  40. package/dist/tools/drive/permissions/drive_remove_permission.js +71 -0
  41. package/dist/tools/drive/permissions/drive_share_file.js +116 -0
  42. package/dist/tools/drive/permissions/drive_update_permission.js +79 -0
  43. package/dist/tools/gmail/basic/gmail_get_message.js +95 -0
  44. package/dist/tools/gmail/basic/gmail_get_thread.js +46 -0
  45. package/dist/tools/gmail/basic/gmail_list_labels.js +54 -0
  46. package/dist/tools/gmail/basic/gmail_search_messages.js +59 -0
  47. package/dist/tools/gmail/batch/gmail_batch_modify_labels.js +74 -0
  48. package/dist/tools/gmail/batch/gmail_get_messages_batch.js +120 -0
  49. package/dist/tools/gmail/batch/gmail_get_threads_batch.js +102 -0
  50. package/dist/tools/gmail/labels/gmail_manage_label.js +131 -0
  51. package/dist/tools/gmail/labels/gmail_modify_labels.js +65 -0
  52. package/dist/tools/gmail/send/gmail_draft_message.js +117 -0
  53. package/dist/tools/gmail/send/gmail_send_message.js +109 -0
  54. package/dist/tools/index.js +267 -3
  55. package/package.json +8 -3
  56. package/dist/tools/basic/gsheets_add_sheet.js +0 -65
  57. package/dist/tools/basic/gsheets_copy_sheet.js +0 -56
  58. package/dist/tools/basic/gsheets_copy_to.js +0 -113
  59. package/dist/tools/basic/gsheets_create_spreadsheet.js +0 -88
  60. package/dist/tools/basic/gsheets_delete_columns.js +0 -69
  61. package/dist/tools/basic/gsheets_delete_rows.js +0 -69
  62. package/dist/tools/basic/gsheets_delete_sheet.js +0 -56
  63. package/dist/tools/basic/gsheets_duplicate_sheet.js +0 -72
  64. package/dist/tools/basic/gsheets_insert_columns.js +0 -69
  65. package/dist/tools/basic/gsheets_insert_rows.js +0 -69
  66. package/dist/tools/basic/gsheets_list_sheets.js +0 -53
  67. package/dist/tools/basic/gsheets_read.js +0 -120
  68. package/dist/tools/basic/gsheets_rename_sheet.js +0 -64
  69. package/dist/tools/charts/gsheets_add_bubble.js +0 -176
  70. package/dist/tools/charts/gsheets_add_candlestick.js +0 -192
  71. package/dist/tools/charts/gsheets_add_chart.js +0 -162
  72. package/dist/tools/charts/gsheets_add_combo.js +0 -169
  73. package/dist/tools/charts/gsheets_add_histogram.js +0 -143
  74. package/dist/tools/charts/gsheets_add_org_chart.js +0 -160
  75. package/dist/tools/charts/gsheets_add_treemap.js +0 -177
  76. package/dist/tools/charts/gsheets_add_waterfall.js +0 -155
  77. package/dist/tools/charts/gsheets_delete_chart.js +0 -56
  78. package/dist/tools/charts/gsheets_update_chart.js +0 -118
  79. package/dist/tools/data/gsheets_append_data.js +0 -68
  80. package/dist/tools/data/gsheets_batch_clear.js +0 -53
  81. package/dist/tools/data/gsheets_batch_update.js +0 -81
  82. package/dist/tools/data/gsheets_clear_data.js +0 -53
  83. package/dist/tools/data/gsheets_create_filter.js +0 -81
  84. package/dist/tools/data/gsheets_find_replace.js +0 -124
  85. package/dist/tools/data/gsheets_set_data_validation.js +0 -153
  86. package/dist/tools/data/gsheets_sort_range.js +0 -102
  87. package/dist/tools/data/gsheets_update_cell.js +0 -44
  88. package/dist/tools/formatting/gsheets_auto_resize.js +0 -75
  89. package/dist/tools/formatting/gsheets_format_cells.js +0 -161
  90. package/dist/tools/formatting/gsheets_freeze_columns.js +0 -67
  91. package/dist/tools/formatting/gsheets_freeze_rows.js +0 -67
  92. package/dist/tools/formatting/gsheets_merge_cells.js +0 -85
  93. package/dist/tools/formatting/gsheets_set_number_format.js +0 -116
  94. package/dist/tools/formatting/gsheets_unmerge_cells.js +0 -79
  95. package/dist/tools/formatting/gsheets_update_borders.js +0 -212
  96. package/dist/tools/gdrive/gdrive_read_file.js +0 -77
  97. package/dist/tools/gdrive/gdrive_search.js +0 -71
  98. package/dist/tools/gdrive_read_file.js +0 -77
  99. package/dist/tools/gdrive_search.js +0 -71
  100. package/dist/tools/gsheets_add_bubble.js +0 -176
  101. package/dist/tools/gsheets_add_candlestick.js +0 -192
  102. package/dist/tools/gsheets_add_chart.js +0 -162
  103. package/dist/tools/gsheets_add_combo.js +0 -169
  104. package/dist/tools/gsheets_add_conditional_format.js +0 -175
  105. package/dist/tools/gsheets_add_histogram.js +0 -143
  106. package/dist/tools/gsheets_add_named_range.js +0 -87
  107. package/dist/tools/gsheets_add_org_chart.js +0 -160
  108. package/dist/tools/gsheets_add_protected_range.js +0 -127
  109. package/dist/tools/gsheets_add_sheet.js +0 -65
  110. package/dist/tools/gsheets_add_treemap.js +0 -177
  111. package/dist/tools/gsheets_add_waterfall.js +0 -155
  112. package/dist/tools/gsheets_append_data.js +0 -68
  113. package/dist/tools/gsheets_auto_resize.js +0 -75
  114. package/dist/tools/gsheets_batch_clear.js +0 -53
  115. package/dist/tools/gsheets_batch_update.js +0 -81
  116. package/dist/tools/gsheets_clear_data.js +0 -53
  117. package/dist/tools/gsheets_copy_sheet.js +0 -56
  118. package/dist/tools/gsheets_copy_to.js +0 -113
  119. package/dist/tools/gsheets_create_filter.js +0 -81
  120. package/dist/tools/gsheets_create_spreadsheet.js +0 -88
  121. package/dist/tools/gsheets_delete_chart.js +0 -56
  122. package/dist/tools/gsheets_delete_columns.js +0 -69
  123. package/dist/tools/gsheets_delete_named_range.js +0 -56
  124. package/dist/tools/gsheets_delete_protected_range.js +0 -56
  125. package/dist/tools/gsheets_delete_rows.js +0 -69
  126. package/dist/tools/gsheets_delete_sheet.js +0 -56
  127. package/dist/tools/gsheets_duplicate_sheet.js +0 -72
  128. package/dist/tools/gsheets_find_replace.js +0 -124
  129. package/dist/tools/gsheets_format_cells.js +0 -161
  130. package/dist/tools/gsheets_freeze_columns.js +0 -67
  131. package/dist/tools/gsheets_freeze_rows.js +0 -67
  132. package/dist/tools/gsheets_insert_columns.js +0 -69
  133. package/dist/tools/gsheets_insert_rows.js +0 -69
  134. package/dist/tools/gsheets_list_sheets.js +0 -53
  135. package/dist/tools/gsheets_merge_cells.js +0 -85
  136. package/dist/tools/gsheets_read.js +0 -120
  137. package/dist/tools/gsheets_rename_sheet.js +0 -64
  138. package/dist/tools/gsheets_set_data_validation.js +0 -153
  139. package/dist/tools/gsheets_set_number_format.js +0 -116
  140. package/dist/tools/gsheets_sort_range.js +0 -102
  141. package/dist/tools/gsheets_unmerge_cells.js +0 -79
  142. package/dist/tools/gsheets_update_borders.js +0 -212
  143. package/dist/tools/gsheets_update_cell.js +0 -44
  144. package/dist/tools/gsheets_update_chart.js +0 -118
  145. package/dist/tools/gsheets_update_named_range.js +0 -112
  146. package/dist/tools/gsheets_update_protected_range.js +0 -110
  147. package/dist/tools/protection/gsheets_add_conditional_format.js +0 -175
  148. package/dist/tools/protection/gsheets_add_named_range.js +0 -87
  149. package/dist/tools/protection/gsheets_add_protected_range.js +0 -127
  150. package/dist/tools/protection/gsheets_delete_named_range.js +0 -56
  151. package/dist/tools/protection/gsheets_delete_protected_range.js +0 -56
  152. package/dist/tools/protection/gsheets_update_named_range.js +0 -112
  153. package/dist/tools/protection/gsheets_update_protected_range.js +0 -110
  154. /package/dist/tools/drive/{drive_read_file.js → basic/drive_read_file.js} +0 -0
  155. /package/dist/tools/drive/{drive_search.js → basic/drive_search.js} +0 -0
@@ -0,0 +1,204 @@
1
+ /**
2
+ * Gmail Helper Functions
3
+ *
4
+ * Utilities for Gmail API operations including:
5
+ * - Message body extraction and formatting
6
+ * - HTML to text conversion
7
+ * - MIME message preparation
8
+ * - Attachment handling
9
+ * - URL generation
10
+ */
11
+ import nodemailer from "nodemailer";
12
+ import { convert } from "html-to-text";
13
+ /**
14
+ * Extract text and HTML bodies from Gmail message payload
15
+ */
16
+ export function extractMessageBodies(payload) {
17
+ let textBody = "";
18
+ let htmlBody = "";
19
+ function extractParts(part) {
20
+ if (part.mimeType === "text/plain" && part.body?.data) {
21
+ textBody = Buffer.from(part.body.data, "base64").toString("utf-8");
22
+ }
23
+ else if (part.mimeType === "text/html" && part.body?.data) {
24
+ htmlBody = Buffer.from(part.body.data, "base64").toString("utf-8");
25
+ }
26
+ // Recursively process multipart messages
27
+ if (part.parts) {
28
+ for (const subPart of part.parts) {
29
+ extractParts(subPart);
30
+ }
31
+ }
32
+ }
33
+ extractParts(payload);
34
+ return { textBody, htmlBody };
35
+ }
36
+ /**
37
+ * Format body content, preferring plain text but converting HTML if needed
38
+ */
39
+ export function formatBodyContent(textBody, htmlBody) {
40
+ if (textBody) {
41
+ return textBody;
42
+ }
43
+ if (htmlBody) {
44
+ // Convert HTML to readable plain text
45
+ return convert(htmlBody, {
46
+ wordwrap: 80,
47
+ selectors: [
48
+ { selector: "a", options: { ignoreHref: false } },
49
+ { selector: "img", format: "skip" },
50
+ ],
51
+ });
52
+ }
53
+ return "[No text content found]";
54
+ }
55
+ export function extractAttachments(payload) {
56
+ const attachments = [];
57
+ function extractParts(part) {
58
+ if (part.filename && part.body?.attachmentId) {
59
+ attachments.push({
60
+ filename: part.filename,
61
+ mimeType: part.mimeType || "application/octet-stream",
62
+ size: part.body.size || 0,
63
+ attachmentId: part.body.attachmentId,
64
+ });
65
+ }
66
+ if (part.parts) {
67
+ for (const subPart of part.parts) {
68
+ extractParts(subPart);
69
+ }
70
+ }
71
+ }
72
+ extractParts(payload);
73
+ return attachments;
74
+ }
75
+ export function prepareGmailMessage(options) {
76
+ const { to, subject, body, bodyFormat, from, cc, bcc, inReplyTo, references, } = options;
77
+ // Build message using nodemailer's createTransport (for MIME creation only)
78
+ const message = {
79
+ from: from || "me",
80
+ to,
81
+ subject,
82
+ [bodyFormat === "html" ? "html" : "text"]: body,
83
+ };
84
+ if (cc)
85
+ message.cc = cc;
86
+ if (bcc)
87
+ message.bcc = bcc;
88
+ if (inReplyTo)
89
+ message.inReplyTo = inReplyTo;
90
+ if (references)
91
+ message.references = references;
92
+ // Create MIME message
93
+ const transport = nodemailer.createTransport({ jsonTransport: true });
94
+ // We'll use a synchronous approach here
95
+ let mimeMessage = "";
96
+ mimeMessage += `From: ${message.from}\r\n`;
97
+ mimeMessage += `To: ${message.to}\r\n`;
98
+ if (message.cc)
99
+ mimeMessage += `Cc: ${message.cc}\r\n`;
100
+ if (message.bcc)
101
+ mimeMessage += `Bcc: ${message.bcc}\r\n`;
102
+ mimeMessage += `Subject: ${message.subject}\r\n`;
103
+ if (message.inReplyTo)
104
+ mimeMessage += `In-Reply-To: ${message.inReplyTo}\r\n`;
105
+ if (message.references)
106
+ mimeMessage += `References: ${message.references}\r\n`;
107
+ mimeMessage += `Content-Type: text/${bodyFormat}; charset=utf-8\r\n`;
108
+ mimeMessage += `\r\n`;
109
+ mimeMessage += body;
110
+ // Base64url encode
111
+ return Buffer.from(mimeMessage)
112
+ .toString("base64")
113
+ .replace(/\+/g, "-")
114
+ .replace(/\//g, "_")
115
+ .replace(/=+$/, "");
116
+ }
117
+ /**
118
+ * Generate Gmail web URL for a message or thread
119
+ */
120
+ export function generateGmailWebUrl(messageId, threadId) {
121
+ if (messageId) {
122
+ return `https://mail.google.com/mail/u/0/#all/${messageId}`;
123
+ }
124
+ if (threadId) {
125
+ return `https://mail.google.com/mail/u/0/#all/${threadId}`;
126
+ }
127
+ return "https://mail.google.com/mail/u/0/";
128
+ }
129
+ /**
130
+ * Extract header value from message headers
131
+ */
132
+ export function getHeader(headers, name) {
133
+ if (!headers)
134
+ return "";
135
+ const header = headers.find((h) => h.name?.toLowerCase() === name.toLowerCase());
136
+ return header?.value || "";
137
+ }
138
+ /**
139
+ * Format thread content for display
140
+ */
141
+ export function formatThreadContent(thread) {
142
+ const messages = thread.messages || [];
143
+ const threadId = thread.id || "";
144
+ if (messages.length === 0) {
145
+ return "No messages in thread";
146
+ }
147
+ // Extract thread subject from first message
148
+ const firstMessage = messages[0];
149
+ const subject = getHeader(firstMessage.payload?.headers, "Subject");
150
+ let output = `Thread ID: ${threadId}\n`;
151
+ output += `Subject: ${subject}\n`;
152
+ output += `Messages: ${messages.length}\n`;
153
+ output += `Web URL: ${generateGmailWebUrl(undefined, threadId)}\n\n`;
154
+ output += "=".repeat(80) + "\n\n";
155
+ // Format each message
156
+ messages.forEach((message, index) => {
157
+ const headers = message.payload?.headers || [];
158
+ const from = getHeader(headers, "From");
159
+ const date = getHeader(headers, "Date");
160
+ const msgSubject = getHeader(headers, "Subject");
161
+ output += `Message ${index + 1}/${messages.length}\n`;
162
+ output += `-`.repeat(40) + "\n";
163
+ output += `From: ${from}\n`;
164
+ output += `Date: ${date}\n`;
165
+ // Only show subject if different from thread subject
166
+ if (msgSubject && msgSubject !== subject) {
167
+ output += `Subject: ${msgSubject}\n`;
168
+ }
169
+ output += `\n`;
170
+ // Extract and format body
171
+ if (message.payload) {
172
+ const { textBody, htmlBody } = extractMessageBodies(message.payload);
173
+ const bodyContent = formatBodyContent(textBody, htmlBody);
174
+ // Truncate very long messages
175
+ const maxLength = 2000;
176
+ if (bodyContent.length > maxLength) {
177
+ output += bodyContent.substring(0, maxLength);
178
+ output += `\n\n[... truncated ${bodyContent.length - maxLength} characters ...]`;
179
+ }
180
+ else {
181
+ output += bodyContent;
182
+ }
183
+ }
184
+ output += "\n\n";
185
+ });
186
+ return output;
187
+ }
188
+ /**
189
+ * Format message list for search results
190
+ */
191
+ export function formatMessageList(messages, nextPageToken) {
192
+ let output = `Found ${messages.length} message(s)\n\n`;
193
+ messages.forEach((msg, index) => {
194
+ const messageId = msg.id || "";
195
+ const threadId = msg.threadId || "";
196
+ output += `${index + 1}. Message ID: ${messageId}\n`;
197
+ output += ` Thread ID: ${threadId}\n`;
198
+ output += ` Web URL: ${generateGmailWebUrl(messageId)}\n\n`;
199
+ });
200
+ if (nextPageToken) {
201
+ output += `\nMore results available. Use pageToken: ${nextPageToken}`;
202
+ }
203
+ return output;
204
+ }
@@ -0,0 +1,80 @@
1
+ import { google } from "googleapis";
2
+ import { ResponseFormatter } from "../../../lib/response-formatter.js";
3
+ export const schema = {
4
+ name: "calendar_acl_insert",
5
+ description: "Add a new access control rule to share a calendar with a user, group, or domain. Specify the access level (owner, writer, reader, freeBusyReader) and the scope (who gets access). Use this to grant calendar permissions to others. The system can optionally send notification emails to inform users about the new sharing.",
6
+ inputSchema: {
7
+ type: "object",
8
+ properties: {
9
+ calendarId: {
10
+ type: "string",
11
+ description: "Calendar identifier. Use 'primary' for the user's primary calendar.",
12
+ },
13
+ role: {
14
+ type: "string",
15
+ description: "Access level: 'owner', 'writer', 'reader', 'freeBusyReader', or 'none'",
16
+ },
17
+ scopeType: {
18
+ type: "string",
19
+ description: "Scope type: 'user' (individual), 'group' (Google group), 'domain' (entire domain), or 'default' (public)",
20
+ },
21
+ scopeValue: {
22
+ type: "string",
23
+ description: "Email address (for user/group) or domain name (for domain). Omit for 'default' scope type.",
24
+ },
25
+ sendNotifications: {
26
+ type: "boolean",
27
+ description: "Whether to send email notifications about the sharing change (default: true)",
28
+ },
29
+ },
30
+ required: ["calendarId", "role", "scopeType"],
31
+ },
32
+ };
33
+ export async function insertAcl(args) {
34
+ try {
35
+ const calendar = google.calendar("v3");
36
+ const { calendarId, role, scopeType, scopeValue, sendNotifications = true, } = args;
37
+ // Validate scope
38
+ if ((scopeType === "user" || scopeType === "group" || scopeType === "domain") && !scopeValue) {
39
+ return ResponseFormatter.error(new Error(`scopeValue is required for scopeType '${scopeType}'. Provide an email address or domain name.`));
40
+ }
41
+ if (scopeType === "default" && scopeValue) {
42
+ return ResponseFormatter.error(new Error("scopeValue should not be provided for scopeType 'default' (public access)."));
43
+ }
44
+ const response = await calendar.acl.insert({
45
+ calendarId,
46
+ sendNotifications,
47
+ requestBody: {
48
+ role,
49
+ scope: {
50
+ type: scopeType,
51
+ value: scopeValue,
52
+ },
53
+ },
54
+ });
55
+ const rule = response.data;
56
+ let message = `✅ Access control rule added successfully\n\n`;
57
+ message += `📅 Calendar: ${calendarId}\n`;
58
+ message += `Role: ${rule.role?.toUpperCase()}\n`;
59
+ message += `Scope: ${rule.scope?.type}`;
60
+ if (rule.scope?.value) {
61
+ message += ` (${rule.scope.value})`;
62
+ }
63
+ message += `\n`;
64
+ message += `Rule ID: ${rule.id}\n`;
65
+ if (sendNotifications && rule.scope?.value) {
66
+ message += `\n📧 Notification email sent to ${rule.scope.value}`;
67
+ }
68
+ message += `\n\n💡 The specified user/group now has ${role} access to this calendar.`;
69
+ return ResponseFormatter.success({
70
+ ruleId: rule.id,
71
+ calendarId,
72
+ role: rule.role,
73
+ scopeType: rule.scope?.type,
74
+ scopeValue: rule.scope?.value || null,
75
+ }, message);
76
+ }
77
+ catch (error) {
78
+ return ResponseFormatter.error(error);
79
+ }
80
+ }
@@ -0,0 +1,82 @@
1
+ import { google } from "googleapis";
2
+ import { ResponseFormatter } from "../../../lib/response-formatter.js";
3
+ export const schema = {
4
+ name: "calendar_acl_list",
5
+ description: "List all access control rules (sharing settings) for a calendar. Shows who has access to the calendar and their permission levels (owner, writer, reader, freeBusyReader). Use this to view current sharing configuration before adding or removing permissions.",
6
+ inputSchema: {
7
+ type: "object",
8
+ properties: {
9
+ calendarId: {
10
+ type: "string",
11
+ description: "Calendar identifier. Use 'primary' for the user's primary calendar.",
12
+ },
13
+ maxResults: {
14
+ type: "number",
15
+ description: "Maximum number of ACL entries to return per page (default: 100, max: 250)",
16
+ },
17
+ pageToken: {
18
+ type: "string",
19
+ description: "Token for accessing subsequent result pages",
20
+ },
21
+ showDeleted: {
22
+ type: "boolean",
23
+ description: "Include deleted ACL entries (default: false)",
24
+ },
25
+ },
26
+ required: ["calendarId"],
27
+ },
28
+ };
29
+ export async function listAcl(args) {
30
+ try {
31
+ const calendar = google.calendar("v3");
32
+ const { calendarId, maxResults, pageToken, showDeleted, } = args;
33
+ const response = await calendar.acl.list({
34
+ calendarId,
35
+ maxResults,
36
+ pageToken,
37
+ showDeleted,
38
+ });
39
+ const rules = response.data.items || [];
40
+ const totalRules = rules.length;
41
+ if (totalRules === 0) {
42
+ return ResponseFormatter.success({ rules: [], count: 0 }, "No access control rules found for this calendar.");
43
+ }
44
+ let message = `🔐 Calendar Access Control List (${totalRules} rule${totalRules > 1 ? "s" : ""})\n\n`;
45
+ rules.forEach((rule, index) => {
46
+ message += `${index + 1}. ${rule.role?.toUpperCase()}\n`;
47
+ message += ` Scope: ${rule.scope?.type}`;
48
+ if (rule.scope?.value) {
49
+ message += ` (${rule.scope.value})`;
50
+ }
51
+ message += `\n`;
52
+ message += ` Rule ID: ${rule.id}\n`;
53
+ if (rule.scope?.type === "user" || rule.scope?.type === "group") {
54
+ message += ` Email: ${rule.scope.value}\n`;
55
+ }
56
+ else if (rule.scope?.type === "domain") {
57
+ message += ` Domain: ${rule.scope.value}\n`;
58
+ }
59
+ else if (rule.scope?.type === "default") {
60
+ message += ` Public Access\n`;
61
+ }
62
+ message += `\n`;
63
+ });
64
+ if (response.data.nextPageToken) {
65
+ message += `📄 Next Page Token: ${response.data.nextPageToken}\n`;
66
+ }
67
+ message += `\n💡 Role levels: owner > writer > reader > freeBusyReader`;
68
+ return ResponseFormatter.success({
69
+ rules: rules.map((rule) => ({
70
+ id: rule.id,
71
+ role: rule.role,
72
+ scopeType: rule.scope?.type,
73
+ scopeValue: rule.scope?.value || null,
74
+ })),
75
+ count: totalRules,
76
+ nextPageToken: response.data.nextPageToken || null,
77
+ }, message);
78
+ }
79
+ catch (error) {
80
+ return ResponseFormatter.error(error);
81
+ }
82
+ }
@@ -0,0 +1,113 @@
1
+ import { google } from "googleapis";
2
+ import { ResponseFormatter } from "../../../lib/response-formatter.js";
3
+ import { formatEvent, parseDateTime, formatAttendees, validateTimeRange } from "../../../lib/calendar-helpers.js";
4
+ export const schema = {
5
+ name: "calendar_create_event",
6
+ description: "Create a new event in Google Calendar with specified details. Supports setting title, time, location, description, attendees, and conference data (Google Meet). Can create all-day events or timed events with specific start/end times.",
7
+ inputSchema: {
8
+ type: "object",
9
+ properties: {
10
+ calendarId: {
11
+ type: "string",
12
+ description: "Calendar identifier (default: 'primary' for user's main calendar)",
13
+ },
14
+ summary: {
15
+ type: "string",
16
+ description: "Event title/summary",
17
+ },
18
+ startTime: {
19
+ type: "string",
20
+ description: "Event start time (ISO 8601 format: '2024-01-15T10:00:00Z' or date only: '2024-01-15' for all-day)",
21
+ },
22
+ endTime: {
23
+ type: "string",
24
+ description: "Event end time (ISO 8601 format: '2024-01-15T11:00:00Z' or date only: '2024-01-16' for all-day)",
25
+ },
26
+ location: {
27
+ type: "string",
28
+ description: "Event location (address or place name)",
29
+ },
30
+ description: {
31
+ type: "string",
32
+ description: "Event description (supports plain text and HTML)",
33
+ },
34
+ attendees: {
35
+ type: "array",
36
+ items: {
37
+ type: "string",
38
+ },
39
+ description: "List of attendee email addresses",
40
+ },
41
+ sendUpdates: {
42
+ type: "string",
43
+ description: "Whether to send event notifications: 'all' (send to all attendees), 'externalOnly' (external only), 'none' (no notifications). Default: 'none'",
44
+ },
45
+ conferenceData: {
46
+ type: "boolean",
47
+ description: "Whether to create a Google Meet conference link (default: false)",
48
+ },
49
+ },
50
+ required: ["summary", "startTime", "endTime"],
51
+ },
52
+ };
53
+ export async function createEvent(args) {
54
+ try {
55
+ const calendar = google.calendar("v3");
56
+ const { calendarId = "primary", summary, startTime, endTime, location, description, attendees, sendUpdates = "none", conferenceData = false, } = args;
57
+ // Validate time range
58
+ const validation = validateTimeRange(startTime, endTime);
59
+ if (!validation.valid) {
60
+ return ResponseFormatter.error(new Error(validation.error));
61
+ }
62
+ // Parse start and end times
63
+ const start = parseDateTime(startTime);
64
+ const end = parseDateTime(endTime);
65
+ // Build event resource
66
+ const eventResource = {
67
+ summary,
68
+ start,
69
+ end,
70
+ };
71
+ if (location) {
72
+ eventResource.location = location;
73
+ }
74
+ if (description) {
75
+ eventResource.description = description;
76
+ }
77
+ if (attendees && attendees.length > 0) {
78
+ eventResource.attendees = formatAttendees(attendees);
79
+ }
80
+ if (conferenceData) {
81
+ eventResource.conferenceData = {
82
+ createRequest: {
83
+ requestId: `meet-${Date.now()}`,
84
+ conferenceSolutionKey: {
85
+ type: "hangoutsMeet",
86
+ },
87
+ },
88
+ };
89
+ }
90
+ const response = await calendar.events.insert({
91
+ calendarId,
92
+ requestBody: eventResource,
93
+ sendUpdates,
94
+ conferenceDataVersion: conferenceData ? 1 : undefined,
95
+ });
96
+ const event = response.data;
97
+ if (!event) {
98
+ return ResponseFormatter.error(new Error("Failed to create event"));
99
+ }
100
+ const formattedOutput = formatEvent(event, calendarId);
101
+ return ResponseFormatter.success({
102
+ id: event.id,
103
+ summary: event.summary,
104
+ start: event.start,
105
+ end: event.end,
106
+ htmlLink: event.htmlLink,
107
+ conferenceData: event.conferenceData,
108
+ }, `Event created successfully:\n\n${formattedOutput}`);
109
+ }
110
+ catch (error) {
111
+ return ResponseFormatter.error(error);
112
+ }
113
+ }
@@ -0,0 +1,52 @@
1
+ import { google } from "googleapis";
2
+ import { ResponseFormatter } from "../../../lib/response-formatter.js";
3
+ export const schema = {
4
+ name: "calendar_delete_event",
5
+ description: "Delete a calendar event permanently. This action cannot be undone. Optionally send notifications to attendees about the event cancellation.",
6
+ inputSchema: {
7
+ type: "object",
8
+ properties: {
9
+ calendarId: {
10
+ type: "string",
11
+ description: "Calendar identifier (default: 'primary' for user's main calendar)",
12
+ },
13
+ eventId: {
14
+ type: "string",
15
+ description: "Event identifier to delete",
16
+ },
17
+ sendUpdates: {
18
+ type: "string",
19
+ description: "Whether to send event cancellation notifications: 'all', 'externalOnly', 'none'. Default: 'all'",
20
+ },
21
+ },
22
+ required: ["eventId"],
23
+ },
24
+ };
25
+ export async function deleteEvent(args) {
26
+ try {
27
+ const calendar = google.calendar("v3");
28
+ const { calendarId = "primary", eventId, sendUpdates = "all", } = args;
29
+ // Get event details before deletion for confirmation message
30
+ const eventResponse = await calendar.events.get({
31
+ calendarId,
32
+ eventId,
33
+ });
34
+ const event = eventResponse.data;
35
+ const eventTitle = event.summary || "(No title)";
36
+ // Delete the event
37
+ await calendar.events.delete({
38
+ calendarId,
39
+ eventId,
40
+ sendUpdates,
41
+ });
42
+ return ResponseFormatter.success({
43
+ deleted: true,
44
+ eventId,
45
+ calendarId,
46
+ summary: eventTitle,
47
+ }, `Event "${eventTitle}" (ID: ${eventId}) has been deleted successfully.`);
48
+ }
49
+ catch (error) {
50
+ return ResponseFormatter.error(error);
51
+ }
52
+ }
@@ -0,0 +1,52 @@
1
+ import { google } from "googleapis";
2
+ import { ResponseFormatter } from "../../../lib/response-formatter.js";
3
+ import { formatEvent } from "../../../lib/calendar-helpers.js";
4
+ export const schema = {
5
+ name: "calendar_get_event",
6
+ description: "Get detailed information about a specific calendar event by its ID. Returns comprehensive event details including title, time, location, description, attendees, recurrence rules, and conference data (Google Meet links).",
7
+ inputSchema: {
8
+ type: "object",
9
+ properties: {
10
+ calendarId: {
11
+ type: "string",
12
+ description: "Calendar identifier (default: 'primary' for user's main calendar)",
13
+ },
14
+ eventId: {
15
+ type: "string",
16
+ description: "Event identifier to retrieve",
17
+ },
18
+ },
19
+ required: ["eventId"],
20
+ },
21
+ };
22
+ export async function getEvent(args) {
23
+ try {
24
+ const calendar = google.calendar("v3");
25
+ const { calendarId = "primary", eventId } = args;
26
+ const response = await calendar.events.get({
27
+ calendarId,
28
+ eventId,
29
+ });
30
+ const event = response.data;
31
+ if (!event) {
32
+ return ResponseFormatter.error(new Error(`Event not found: ${eventId}`));
33
+ }
34
+ const formattedOutput = formatEvent(event, calendarId);
35
+ return ResponseFormatter.success({
36
+ id: event.id,
37
+ summary: event.summary,
38
+ start: event.start,
39
+ end: event.end,
40
+ location: event.location,
41
+ description: event.description,
42
+ attendees: event.attendees,
43
+ htmlLink: event.htmlLink,
44
+ conferenceData: event.conferenceData,
45
+ recurrence: event.recurrence,
46
+ status: event.status,
47
+ }, `Event details:\n\n${formattedOutput}`);
48
+ }
49
+ catch (error) {
50
+ return ResponseFormatter.error(error);
51
+ }
52
+ }
@@ -0,0 +1,86 @@
1
+ import { google } from "googleapis";
2
+ import { ResponseFormatter } from "../../../lib/response-formatter.js";
3
+ import { formatEventList } from "../../../lib/calendar-helpers.js";
4
+ export const schema = {
5
+ name: "calendar_list_events",
6
+ description: "List events from a Google Calendar. Returns upcoming events with details including title, time, location, attendees, and meeting links. Supports filtering by time range, search query, and pagination.",
7
+ inputSchema: {
8
+ type: "object",
9
+ properties: {
10
+ calendarId: {
11
+ type: "string",
12
+ description: "Calendar identifier (default: 'primary' for user's main calendar)",
13
+ },
14
+ timeMin: {
15
+ type: "string",
16
+ description: "Lower bound (inclusive) for event start time (ISO 8601 format, e.g., '2024-01-01T00:00:00Z')",
17
+ },
18
+ timeMax: {
19
+ type: "string",
20
+ description: "Upper bound (exclusive) for event end time (ISO 8601 format)",
21
+ },
22
+ q: {
23
+ type: "string",
24
+ description: "Free text search terms to find events matching title, description, location, attendee emails, etc.",
25
+ },
26
+ maxResults: {
27
+ type: "number",
28
+ description: "Maximum number of events to return (default: 10, max: 250)",
29
+ },
30
+ pageToken: {
31
+ type: "string",
32
+ description: "Token for pagination to get the next page of results",
33
+ },
34
+ singleEvents: {
35
+ type: "boolean",
36
+ description: "Whether to expand recurring events into instances (default: true)",
37
+ },
38
+ orderBy: {
39
+ type: "string",
40
+ description: "Order of events: 'startTime' (chronological) or 'updated' (modification time). Requires singleEvents=true for 'startTime'",
41
+ },
42
+ },
43
+ required: [],
44
+ },
45
+ };
46
+ export async function listEvents(args) {
47
+ try {
48
+ const calendar = google.calendar("v3");
49
+ const { calendarId = "primary", timeMin, timeMax, q, maxResults = 10, pageToken, singleEvents = true, orderBy = "startTime", } = args;
50
+ const response = await calendar.events.list({
51
+ calendarId,
52
+ timeMin,
53
+ timeMax,
54
+ q,
55
+ maxResults: Math.min(maxResults, 250),
56
+ pageToken,
57
+ singleEvents,
58
+ orderBy: singleEvents ? orderBy : undefined,
59
+ });
60
+ const events = response.data.items || [];
61
+ const nextPageToken = response.data.nextPageToken;
62
+ if (events.length === 0) {
63
+ return ResponseFormatter.success({
64
+ count: 0,
65
+ events: [],
66
+ calendarId,
67
+ }, `No events found in calendar: ${calendarId}`);
68
+ }
69
+ const formattedOutput = formatEventList(events, calendarId);
70
+ return ResponseFormatter.success({
71
+ count: events.length,
72
+ calendarId,
73
+ events: events.map((e) => ({
74
+ id: e.id,
75
+ summary: e.summary,
76
+ start: e.start,
77
+ end: e.end,
78
+ htmlLink: e.htmlLink,
79
+ })),
80
+ nextPageToken,
81
+ }, `Events from ${calendarId}:\n\n${formattedOutput}`);
82
+ }
83
+ catch (error) {
84
+ return ResponseFormatter.error(error);
85
+ }
86
+ }