@alanse/mcp-server-google-workspace 0.2.1 → 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 (182) hide show
  1. package/README.md +250 -17
  2. package/dist/auth.js +6 -0
  3. package/dist/index.js +1 -1
  4. package/dist/lib/calendar-helpers.js +197 -0
  5. package/dist/lib/document-id-resolver.js +76 -0
  6. package/dist/lib/drive-helpers.js +263 -0
  7. package/dist/lib/gmail-helpers.js +204 -0
  8. package/dist/lib/response-formatter.js +82 -0
  9. package/dist/lib/validation.js +112 -0
  10. package/dist/tools/calendar/acl/calendar_acl_insert.js +80 -0
  11. package/dist/tools/calendar/acl/calendar_acl_list.js +82 -0
  12. package/dist/tools/calendar/basic/calendar_create_event.js +113 -0
  13. package/dist/tools/calendar/basic/calendar_delete_event.js +52 -0
  14. package/dist/tools/calendar/basic/calendar_get_event.js +52 -0
  15. package/dist/tools/calendar/basic/calendar_list_events.js +86 -0
  16. package/dist/tools/calendar/basic/calendar_update_event.js +116 -0
  17. package/dist/tools/calendar/calendarlist/calendar_calendarlist_get.js +73 -0
  18. package/dist/tools/calendar/calendarlist/calendar_calendarlist_list.js +87 -0
  19. package/dist/tools/calendar/calendars/calendar_calendars_get.js +52 -0
  20. package/dist/tools/calendar/calendars/calendar_calendars_insert.js +66 -0
  21. package/dist/tools/calendar/calendars/calendar_calendars_update.js +85 -0
  22. package/dist/tools/calendar/colors/calendar_colors_get.js +46 -0
  23. package/dist/tools/calendar/events_advanced/calendar_events_instances.js +81 -0
  24. package/dist/tools/calendar/events_advanced/calendar_events_move.js +63 -0
  25. package/dist/tools/calendar/events_advanced/calendar_events_quickadd.js +52 -0
  26. package/dist/tools/calendar/freebusy/calendar_freebusy_query.js +69 -0
  27. package/dist/tools/calendar/settings/calendar_settings_list.js +81 -0
  28. package/dist/tools/docs/basic/gdocs_create.js +37 -0
  29. package/dist/tools/docs/basic/gdocs_get_metadata.js +45 -0
  30. package/dist/tools/docs/basic/gdocs_list_documents.js +59 -0
  31. package/dist/tools/docs/basic/gdocs_read.js +62 -0
  32. package/dist/tools/docs/content/gdocs_append_text.js +57 -0
  33. package/dist/tools/docs/content/gdocs_apply_style.js +86 -0
  34. package/dist/tools/docs/content/gdocs_create_heading.js +89 -0
  35. package/dist/tools/docs/content/gdocs_create_list.js +86 -0
  36. package/dist/tools/docs/content/gdocs_delete_text.js +64 -0
  37. package/dist/tools/docs/content/gdocs_format_text.js +137 -0
  38. package/dist/tools/docs/content/gdocs_insert_text.js +62 -0
  39. package/dist/tools/docs/content/gdocs_replace_text.js +64 -0
  40. package/dist/tools/docs/content/gdocs_set_alignment.js +76 -0
  41. package/dist/tools/docs/content/gdocs_update_text.js +78 -0
  42. package/dist/tools/docs/elements/gdocs_batch_update.js +108 -0
  43. package/dist/tools/docs/elements/gdocs_create_table.js +73 -0
  44. package/dist/tools/docs/elements/gdocs_export.js +62 -0
  45. package/dist/tools/docs/elements/gdocs_insert_image.js +96 -0
  46. package/dist/tools/docs/elements/gdocs_insert_link.js +77 -0
  47. package/dist/tools/docs/elements/gdocs_insert_page_break.js +55 -0
  48. package/dist/tools/docs/elements/gdocs_insert_toc.js +71 -0
  49. package/dist/tools/docs/elements/gdocs_merge_documents.js +104 -0
  50. package/dist/tools/docs/elements/gdocs_suggest_mode.js +41 -0
  51. package/dist/tools/drive/advanced/drive_empty_trash.js +56 -0
  52. package/dist/tools/drive/advanced/drive_export_file.js +158 -0
  53. package/dist/tools/drive/advanced/drive_list_revisions.js +80 -0
  54. package/dist/tools/drive/basic/drive_get_metadata.js +49 -0
  55. package/dist/tools/drive/basic/drive_list_files.js +76 -0
  56. package/dist/tools/drive/file/drive_copy_file.js +79 -0
  57. package/dist/tools/drive/file/drive_create_file.js +72 -0
  58. package/dist/tools/drive/file/drive_delete_file.js +48 -0
  59. package/dist/tools/drive/file/drive_move_file.js +79 -0
  60. package/dist/tools/drive/file/drive_rename_file.js +58 -0
  61. package/dist/tools/drive/file/drive_update_file.js +106 -0
  62. package/dist/tools/drive/file/drive_upload_file.js +80 -0
  63. package/dist/tools/drive/folder/drive_create_folder.js +67 -0
  64. package/dist/tools/drive/folder/drive_list_folder_contents.js +68 -0
  65. package/dist/tools/drive/folder/drive_move_to_folder.js +59 -0
  66. package/dist/tools/drive/permissions/drive_list_permissions.js +115 -0
  67. package/dist/tools/drive/permissions/drive_remove_permission.js +71 -0
  68. package/dist/tools/drive/permissions/drive_share_file.js +116 -0
  69. package/dist/tools/drive/permissions/drive_update_permission.js +79 -0
  70. package/dist/tools/gmail/basic/gmail_get_message.js +95 -0
  71. package/dist/tools/gmail/basic/gmail_get_thread.js +46 -0
  72. package/dist/tools/gmail/basic/gmail_list_labels.js +54 -0
  73. package/dist/tools/gmail/basic/gmail_search_messages.js +59 -0
  74. package/dist/tools/gmail/batch/gmail_batch_modify_labels.js +74 -0
  75. package/dist/tools/gmail/batch/gmail_get_messages_batch.js +120 -0
  76. package/dist/tools/gmail/batch/gmail_get_threads_batch.js +102 -0
  77. package/dist/tools/gmail/labels/gmail_manage_label.js +131 -0
  78. package/dist/tools/gmail/labels/gmail_modify_labels.js +65 -0
  79. package/dist/tools/gmail/send/gmail_draft_message.js +117 -0
  80. package/dist/tools/gmail/send/gmail_send_message.js +109 -0
  81. package/dist/tools/index.js +386 -3
  82. package/package.json +8 -3
  83. package/dist/tools/basic/gsheets_add_sheet.js +0 -65
  84. package/dist/tools/basic/gsheets_copy_sheet.js +0 -56
  85. package/dist/tools/basic/gsheets_copy_to.js +0 -113
  86. package/dist/tools/basic/gsheets_create_spreadsheet.js +0 -88
  87. package/dist/tools/basic/gsheets_delete_columns.js +0 -69
  88. package/dist/tools/basic/gsheets_delete_rows.js +0 -69
  89. package/dist/tools/basic/gsheets_delete_sheet.js +0 -56
  90. package/dist/tools/basic/gsheets_duplicate_sheet.js +0 -72
  91. package/dist/tools/basic/gsheets_insert_columns.js +0 -69
  92. package/dist/tools/basic/gsheets_insert_rows.js +0 -69
  93. package/dist/tools/basic/gsheets_list_sheets.js +0 -53
  94. package/dist/tools/basic/gsheets_read.js +0 -120
  95. package/dist/tools/basic/gsheets_rename_sheet.js +0 -64
  96. package/dist/tools/charts/gsheets_add_bubble.js +0 -176
  97. package/dist/tools/charts/gsheets_add_candlestick.js +0 -192
  98. package/dist/tools/charts/gsheets_add_chart.js +0 -162
  99. package/dist/tools/charts/gsheets_add_combo.js +0 -169
  100. package/dist/tools/charts/gsheets_add_histogram.js +0 -143
  101. package/dist/tools/charts/gsheets_add_org_chart.js +0 -160
  102. package/dist/tools/charts/gsheets_add_treemap.js +0 -177
  103. package/dist/tools/charts/gsheets_add_waterfall.js +0 -155
  104. package/dist/tools/charts/gsheets_delete_chart.js +0 -56
  105. package/dist/tools/charts/gsheets_update_chart.js +0 -118
  106. package/dist/tools/data/gsheets_append_data.js +0 -68
  107. package/dist/tools/data/gsheets_batch_clear.js +0 -53
  108. package/dist/tools/data/gsheets_batch_update.js +0 -81
  109. package/dist/tools/data/gsheets_clear_data.js +0 -53
  110. package/dist/tools/data/gsheets_create_filter.js +0 -81
  111. package/dist/tools/data/gsheets_find_replace.js +0 -124
  112. package/dist/tools/data/gsheets_set_data_validation.js +0 -153
  113. package/dist/tools/data/gsheets_sort_range.js +0 -102
  114. package/dist/tools/data/gsheets_update_cell.js +0 -44
  115. package/dist/tools/formatting/gsheets_auto_resize.js +0 -75
  116. package/dist/tools/formatting/gsheets_format_cells.js +0 -161
  117. package/dist/tools/formatting/gsheets_freeze_columns.js +0 -67
  118. package/dist/tools/formatting/gsheets_freeze_rows.js +0 -67
  119. package/dist/tools/formatting/gsheets_merge_cells.js +0 -85
  120. package/dist/tools/formatting/gsheets_set_number_format.js +0 -116
  121. package/dist/tools/formatting/gsheets_unmerge_cells.js +0 -79
  122. package/dist/tools/formatting/gsheets_update_borders.js +0 -212
  123. package/dist/tools/gdrive/gdrive_read_file.js +0 -77
  124. package/dist/tools/gdrive/gdrive_search.js +0 -71
  125. package/dist/tools/gdrive_read_file.js +0 -77
  126. package/dist/tools/gdrive_search.js +0 -71
  127. package/dist/tools/gsheets_add_bubble.js +0 -176
  128. package/dist/tools/gsheets_add_candlestick.js +0 -192
  129. package/dist/tools/gsheets_add_chart.js +0 -162
  130. package/dist/tools/gsheets_add_combo.js +0 -169
  131. package/dist/tools/gsheets_add_conditional_format.js +0 -175
  132. package/dist/tools/gsheets_add_histogram.js +0 -143
  133. package/dist/tools/gsheets_add_named_range.js +0 -87
  134. package/dist/tools/gsheets_add_org_chart.js +0 -160
  135. package/dist/tools/gsheets_add_protected_range.js +0 -127
  136. package/dist/tools/gsheets_add_sheet.js +0 -65
  137. package/dist/tools/gsheets_add_treemap.js +0 -177
  138. package/dist/tools/gsheets_add_waterfall.js +0 -155
  139. package/dist/tools/gsheets_append_data.js +0 -68
  140. package/dist/tools/gsheets_auto_resize.js +0 -75
  141. package/dist/tools/gsheets_batch_clear.js +0 -53
  142. package/dist/tools/gsheets_batch_update.js +0 -81
  143. package/dist/tools/gsheets_clear_data.js +0 -53
  144. package/dist/tools/gsheets_copy_sheet.js +0 -56
  145. package/dist/tools/gsheets_copy_to.js +0 -113
  146. package/dist/tools/gsheets_create_filter.js +0 -81
  147. package/dist/tools/gsheets_create_spreadsheet.js +0 -88
  148. package/dist/tools/gsheets_delete_chart.js +0 -56
  149. package/dist/tools/gsheets_delete_columns.js +0 -69
  150. package/dist/tools/gsheets_delete_named_range.js +0 -56
  151. package/dist/tools/gsheets_delete_protected_range.js +0 -56
  152. package/dist/tools/gsheets_delete_rows.js +0 -69
  153. package/dist/tools/gsheets_delete_sheet.js +0 -56
  154. package/dist/tools/gsheets_duplicate_sheet.js +0 -72
  155. package/dist/tools/gsheets_find_replace.js +0 -124
  156. package/dist/tools/gsheets_format_cells.js +0 -161
  157. package/dist/tools/gsheets_freeze_columns.js +0 -67
  158. package/dist/tools/gsheets_freeze_rows.js +0 -67
  159. package/dist/tools/gsheets_insert_columns.js +0 -69
  160. package/dist/tools/gsheets_insert_rows.js +0 -69
  161. package/dist/tools/gsheets_list_sheets.js +0 -53
  162. package/dist/tools/gsheets_merge_cells.js +0 -85
  163. package/dist/tools/gsheets_read.js +0 -120
  164. package/dist/tools/gsheets_rename_sheet.js +0 -64
  165. package/dist/tools/gsheets_set_data_validation.js +0 -153
  166. package/dist/tools/gsheets_set_number_format.js +0 -116
  167. package/dist/tools/gsheets_sort_range.js +0 -102
  168. package/dist/tools/gsheets_unmerge_cells.js +0 -79
  169. package/dist/tools/gsheets_update_borders.js +0 -212
  170. package/dist/tools/gsheets_update_cell.js +0 -44
  171. package/dist/tools/gsheets_update_chart.js +0 -118
  172. package/dist/tools/gsheets_update_named_range.js +0 -112
  173. package/dist/tools/gsheets_update_protected_range.js +0 -110
  174. package/dist/tools/protection/gsheets_add_conditional_format.js +0 -175
  175. package/dist/tools/protection/gsheets_add_named_range.js +0 -87
  176. package/dist/tools/protection/gsheets_add_protected_range.js +0 -127
  177. package/dist/tools/protection/gsheets_delete_named_range.js +0 -56
  178. package/dist/tools/protection/gsheets_delete_protected_range.js +0 -56
  179. package/dist/tools/protection/gsheets_update_named_range.js +0 -112
  180. package/dist/tools/protection/gsheets_update_protected_range.js +0 -110
  181. /package/dist/tools/drive/{drive_read_file.js → basic/drive_read_file.js} +0 -0
  182. /package/dist/tools/drive/{drive_search.js → basic/drive_search.js} +0 -0
@@ -0,0 +1,263 @@
1
+ /**
2
+ * Google Drive Helper Functions
3
+ *
4
+ * Utilities for Google Drive API operations including:
5
+ * - File metadata formatting
6
+ * - Permission formatting
7
+ * - MIME type conversion
8
+ * - URL generation
9
+ * - File upload handling
10
+ */
11
+ /**
12
+ * Format file metadata for display
13
+ */
14
+ export function formatFileMetadata(file, detailed = false) {
15
+ const id = file.id || "";
16
+ const name = file.name || "Unnamed";
17
+ const mimeType = file.mimeType || "unknown";
18
+ const size = file.size ? formatFileSize(parseInt(file.size)) : "N/A";
19
+ const modifiedTime = file.modifiedTime
20
+ ? new Date(file.modifiedTime).toLocaleString()
21
+ : "Unknown";
22
+ const webViewLink = file.webViewLink || "";
23
+ if (!detailed) {
24
+ return `${name} (${id}) - ${mimeType}`;
25
+ }
26
+ let output = `📄 ${name}\n`;
27
+ output += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`;
28
+ output += `ID: ${id}\n`;
29
+ output += `MIME Type: ${mimeType}\n`;
30
+ output += `Size: ${size}\n`;
31
+ output += `Modified: ${modifiedTime}\n`;
32
+ if (webViewLink) {
33
+ output += `Web Link: ${webViewLink}\n`;
34
+ }
35
+ if (file.parents && file.parents.length > 0) {
36
+ output += `Parent Folder(s): ${file.parents.join(", ")}\n`;
37
+ }
38
+ output += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`;
39
+ return output;
40
+ }
41
+ /**
42
+ * Format file list for display
43
+ */
44
+ export function formatFileList(files, nextPageToken) {
45
+ if (files.length === 0) {
46
+ return "No files found.";
47
+ }
48
+ let output = `Found ${files.length} file(s):\n\n`;
49
+ files.forEach((file, index) => {
50
+ const name = file.name || "Unnamed";
51
+ const id = file.id || "";
52
+ const mimeType = file.mimeType || "unknown";
53
+ const size = file.size ? formatFileSize(parseInt(file.size)) : "N/A";
54
+ output += `${index + 1}. ${name}\n`;
55
+ output += ` ID: ${id}\n`;
56
+ output += ` Type: ${mimeType}\n`;
57
+ output += ` Size: ${size}\n`;
58
+ if (file.webViewLink) {
59
+ output += ` Link: ${file.webViewLink}\n`;
60
+ }
61
+ output += `\n`;
62
+ });
63
+ if (nextPageToken) {
64
+ output += `\nMore results available. Use pageToken: ${nextPageToken}`;
65
+ }
66
+ return output;
67
+ }
68
+ /**
69
+ * Format permission for display
70
+ */
71
+ export function formatPermission(permission) {
72
+ const role = permission.role || "unknown";
73
+ const type = permission.type || "unknown";
74
+ const emailAddress = permission.emailAddress || "";
75
+ const displayName = permission.displayName || "";
76
+ const id = permission.id || "";
77
+ let output = `Permission ID: ${id}\n`;
78
+ output += ` Role: ${role}\n`;
79
+ output += ` Type: ${type}\n`;
80
+ if (emailAddress) {
81
+ output += ` Email: ${emailAddress}\n`;
82
+ }
83
+ if (displayName) {
84
+ output += ` Name: ${displayName}\n`;
85
+ }
86
+ return output;
87
+ }
88
+ /**
89
+ * Format permission list for display
90
+ */
91
+ export function formatPermissionList(permissions) {
92
+ if (permissions.length === 0) {
93
+ return "No permissions found.";
94
+ }
95
+ let output = `Total permissions: ${permissions.length}\n\n`;
96
+ permissions.forEach((permission, index) => {
97
+ output += `${index + 1}. ${formatPermission(permission)}\n`;
98
+ });
99
+ return output;
100
+ }
101
+ /**
102
+ * Format revision for display
103
+ */
104
+ export function formatRevision(revision) {
105
+ const id = revision.id || "";
106
+ const modifiedTime = revision.modifiedTime
107
+ ? new Date(revision.modifiedTime).toLocaleString()
108
+ : "Unknown";
109
+ const lastModifyingUser = revision.lastModifyingUser?.displayName || "Unknown";
110
+ const size = revision.size ? formatFileSize(parseInt(revision.size)) : "N/A";
111
+ let output = `Revision ID: ${id}\n`;
112
+ output += ` Modified: ${modifiedTime}\n`;
113
+ output += ` Modified by: ${lastModifyingUser}\n`;
114
+ output += ` Size: ${size}\n`;
115
+ return output;
116
+ }
117
+ /**
118
+ * Format revision list for display
119
+ */
120
+ export function formatRevisionList(revisions) {
121
+ if (revisions.length === 0) {
122
+ return "No revisions found.";
123
+ }
124
+ let output = `Total revisions: ${revisions.length}\n\n`;
125
+ revisions.forEach((revision, index) => {
126
+ output += `${index + 1}. ${formatRevision(revision)}\n`;
127
+ });
128
+ return output;
129
+ }
130
+ /**
131
+ * Convert MIME type for export
132
+ */
133
+ export function convertMimeType(sourceMimeType, targetFormat) {
134
+ // Google Workspace document types
135
+ const googleMimeTypes = {
136
+ "application/vnd.google-apps.document": {
137
+ pdf: "application/pdf",
138
+ docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
139
+ html: "text/html",
140
+ txt: "text/plain",
141
+ odt: "application/vnd.oasis.opendocument.text",
142
+ rtf: "application/rtf",
143
+ epub: "application/epub+zip",
144
+ markdown: "text/markdown",
145
+ },
146
+ "application/vnd.google-apps.spreadsheet": {
147
+ pdf: "application/pdf",
148
+ xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
149
+ csv: "text/csv",
150
+ ods: "application/vnd.oasis.opendocument.spreadsheet",
151
+ tsv: "text/tab-separated-values",
152
+ html: "text/html",
153
+ },
154
+ "application/vnd.google-apps.presentation": {
155
+ pdf: "application/pdf",
156
+ pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
157
+ odp: "application/vnd.oasis.opendocument.presentation",
158
+ txt: "text/plain",
159
+ },
160
+ "application/vnd.google-apps.drawing": {
161
+ pdf: "application/pdf",
162
+ png: "image/png",
163
+ jpeg: "image/jpeg",
164
+ svg: "image/svg+xml",
165
+ },
166
+ };
167
+ if (!targetFormat) {
168
+ // Default export formats
169
+ const defaults = {
170
+ "application/vnd.google-apps.document": "application/pdf",
171
+ "application/vnd.google-apps.spreadsheet": "text/csv",
172
+ "application/vnd.google-apps.presentation": "application/pdf",
173
+ "application/vnd.google-apps.drawing": "image/png",
174
+ };
175
+ return defaults[sourceMimeType] || "application/pdf";
176
+ }
177
+ const formats = googleMimeTypes[sourceMimeType];
178
+ if (!formats) {
179
+ throw new Error(`Unsupported source MIME type: ${sourceMimeType}`);
180
+ }
181
+ const targetMimeType = formats[targetFormat.toLowerCase()];
182
+ if (!targetMimeType) {
183
+ throw new Error(`Unsupported export format '${targetFormat}' for MIME type '${sourceMimeType}'`);
184
+ }
185
+ return targetMimeType;
186
+ }
187
+ /**
188
+ * Generate Google Drive web URL for a file or folder
189
+ */
190
+ export function generateDriveWebUrl(fileId) {
191
+ return `https://drive.google.com/file/d/${fileId}/view`;
192
+ }
193
+ /**
194
+ * Generate folder web URL
195
+ */
196
+ export function generateFolderWebUrl(folderId) {
197
+ return `https://drive.google.com/drive/folders/${folderId}`;
198
+ }
199
+ /**
200
+ * Format file size to human-readable format
201
+ */
202
+ function formatFileSize(bytes) {
203
+ if (bytes === 0)
204
+ return "0 Bytes";
205
+ const k = 1024;
206
+ const sizes = ["Bytes", "KB", "MB", "GB", "TB"];
207
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
208
+ return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + " " + sizes[i];
209
+ }
210
+ /**
211
+ * Get MIME type for folder
212
+ */
213
+ export const FOLDER_MIME_TYPE = "application/vnd.google-apps.folder";
214
+ /**
215
+ * Check if MIME type is a Google Workspace document
216
+ */
217
+ export function isGoogleWorkspaceDocument(mimeType) {
218
+ return mimeType.startsWith("application/vnd.google-apps.");
219
+ }
220
+ /**
221
+ * Get icon for MIME type
222
+ */
223
+ export function getFileIcon(mimeType) {
224
+ const icons = {
225
+ "application/vnd.google-apps.folder": "📁",
226
+ "application/vnd.google-apps.document": "📝",
227
+ "application/vnd.google-apps.spreadsheet": "📊",
228
+ "application/vnd.google-apps.presentation": "📊",
229
+ "application/vnd.google-apps.drawing": "🎨",
230
+ "application/pdf": "📄",
231
+ "image/png": "🖼️",
232
+ "image/jpeg": "🖼️",
233
+ "text/plain": "📃",
234
+ "application/zip": "🗜️",
235
+ };
236
+ return icons[mimeType] || "📄";
237
+ }
238
+ /**
239
+ * Format permission role for display
240
+ */
241
+ export function getRoleLabel(role) {
242
+ const labels = {
243
+ owner: "Owner",
244
+ organizer: "Organizer",
245
+ fileOrganizer: "File Organizer",
246
+ writer: "Editor",
247
+ commenter: "Commenter",
248
+ reader: "Viewer",
249
+ };
250
+ return labels[role] || role;
251
+ }
252
+ /**
253
+ * Format permission type for display
254
+ */
255
+ export function getTypeLabel(type) {
256
+ const labels = {
257
+ user: "User",
258
+ group: "Group",
259
+ domain: "Domain",
260
+ anyone: "Anyone with link",
261
+ };
262
+ return labels[type] || type;
263
+ }
@@ -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,82 @@
1
+ /**
2
+ * ResponseFormatter - Consistent response formatting across all tools
3
+ */
4
+ export class ResponseFormatter {
5
+ /**
6
+ * Create a success response
7
+ */
8
+ static success(data, message) {
9
+ let text;
10
+ if (message) {
11
+ // If message is provided, combine it with data
12
+ if (typeof data === "object" && data !== null) {
13
+ text = `${message}\n\n${JSON.stringify(data, null, 2)}`;
14
+ }
15
+ else {
16
+ text = message;
17
+ }
18
+ }
19
+ else {
20
+ // Format data only
21
+ text = typeof data === "object" && data !== null
22
+ ? JSON.stringify(data, null, 2)
23
+ : String(data);
24
+ }
25
+ return {
26
+ content: [{ type: "text", text }],
27
+ isError: false,
28
+ };
29
+ }
30
+ /**
31
+ * Create an error response
32
+ */
33
+ static error(error) {
34
+ const message = error instanceof Error ? error.message : error;
35
+ return {
36
+ content: [
37
+ {
38
+ type: "text",
39
+ text: `Error: ${message}`,
40
+ },
41
+ ],
42
+ isError: true,
43
+ };
44
+ }
45
+ /**
46
+ * Format Google Docs document data
47
+ */
48
+ static formatDocumentData(doc) {
49
+ const formatted = {
50
+ documentId: doc.documentId,
51
+ title: doc.title,
52
+ revisionId: doc.revisionId,
53
+ suggestionsViewMode: doc.suggestionsViewMode,
54
+ };
55
+ return JSON.stringify(formatted, null, 2);
56
+ }
57
+ /**
58
+ * Format metadata
59
+ */
60
+ static formatMetadata(metadata) {
61
+ return JSON.stringify(metadata, null, 2);
62
+ }
63
+ /**
64
+ * Format a simple success message
65
+ */
66
+ static simpleSuccess(message) {
67
+ return {
68
+ content: [{ type: "text", text: message }],
69
+ isError: false,
70
+ };
71
+ }
72
+ /**
73
+ * Format operation result with details
74
+ */
75
+ static operationResult(operation, details) {
76
+ const text = `${operation} completed successfully\n\n${JSON.stringify(details, null, 2)}`;
77
+ return {
78
+ content: [{ type: "text", text }],
79
+ isError: false,
80
+ };
81
+ }
82
+ }
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Validation - Shared validation logic for tool inputs
3
+ */
4
+ export class Validator {
5
+ /**
6
+ * Validate that required fields exist in an object
7
+ */
8
+ static validateRequired(obj, fields) {
9
+ const missing = fields.filter((field) => obj[field] === undefined);
10
+ if (missing.length > 0) {
11
+ throw new Error(`Missing required fields: ${missing.join(", ")}`);
12
+ }
13
+ }
14
+ /**
15
+ * Validate RGB color values (0-1 range)
16
+ */
17
+ static validateColor(color) {
18
+ const values = [color.red, color.green, color.blue];
19
+ return values.every((v) => typeof v === "number" && v >= 0 && v <= 1);
20
+ }
21
+ /**
22
+ * Validate dimension type
23
+ */
24
+ static validateDimension(dimension) {
25
+ return dimension === "ROWS" || dimension === "COLUMNS";
26
+ }
27
+ /**
28
+ * Validate text style object
29
+ */
30
+ static validateTextStyle(style) {
31
+ // Check for at least one style property
32
+ const validProperties = [
33
+ "bold",
34
+ "italic",
35
+ "underline",
36
+ "fontSize",
37
+ "fontFamily",
38
+ "foregroundColor",
39
+ "backgroundColor",
40
+ ];
41
+ const hasValidProperty = validProperties.some((prop) => style[prop] !== undefined);
42
+ if (!hasValidProperty) {
43
+ return false;
44
+ }
45
+ // Validate color properties if they exist
46
+ if (style.foregroundColor && !this.validateColor(style.foregroundColor)) {
47
+ return false;
48
+ }
49
+ if (style.backgroundColor && !this.validateColor(style.backgroundColor)) {
50
+ return false;
51
+ }
52
+ return true;
53
+ }
54
+ /**
55
+ * Validate index is a positive integer
56
+ */
57
+ static validateIndex(index, allowZero = false) {
58
+ if (!Number.isInteger(index)) {
59
+ return false;
60
+ }
61
+ return allowZero ? index >= 0 : index > 0;
62
+ }
63
+ /**
64
+ * Validate range (startIndex < endIndex)
65
+ */
66
+ static validateRange(startIndex, endIndex) {
67
+ return (this.validateIndex(startIndex) &&
68
+ this.validateIndex(endIndex) &&
69
+ startIndex < endIndex);
70
+ }
71
+ /**
72
+ * Validate alignment value
73
+ */
74
+ static validateAlignment(alignment) {
75
+ return ["START", "CENTER", "END", "JUSTIFIED"].includes(alignment);
76
+ }
77
+ /**
78
+ * Validate named style type
79
+ */
80
+ static validateNamedStyleType(styleType) {
81
+ const validTypes = [
82
+ "NORMAL_TEXT",
83
+ "HEADING_1",
84
+ "HEADING_2",
85
+ "HEADING_3",
86
+ "HEADING_4",
87
+ "HEADING_5",
88
+ "HEADING_6",
89
+ "TITLE",
90
+ "SUBTITLE",
91
+ ];
92
+ return validTypes.includes(styleType);
93
+ }
94
+ /**
95
+ * Validate URL format
96
+ */
97
+ static validateUrl(url) {
98
+ try {
99
+ new URL(url);
100
+ return url.startsWith("http://") || url.startsWith("https://");
101
+ }
102
+ catch {
103
+ return false;
104
+ }
105
+ }
106
+ /**
107
+ * Validate positive number
108
+ */
109
+ static validatePositiveNumber(value) {
110
+ return typeof value === "number" && value > 0 && !isNaN(value);
111
+ }
112
+ }