@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
package/README.md CHANGED
@@ -1,9 +1,9 @@
1
1
  # MCP Server - Google Workspace
2
2
 
3
- **The most comprehensive MCP server for Google Workspace** - Complete programmatic control over Sheets, Docs, Drive, and future Calendar, Forms integration.
3
+ **The most comprehensive MCP server for Google Workspace** - Complete programmatic control over Sheets, Docs, Drive, Gmail, Calendar, and future Forms integration.
4
4
 
5
- 🚀 **Current Status**: 82 tools (Drive: 2, Sheets: 57, Docs: 23)
6
- 📅 **Roadmap**: Calendar, Forms, Gmail, Slides
5
+ 🚀 **Current Status**: 130 tools (Drive: 21, Sheets: 57, Docs: 23, Gmail: 11, Calendar: 18)
6
+ 📅 **Roadmap**: Forms, Slides
7
7
 
8
8
  Extended implementation by Alanse inc.
9
9
 
@@ -13,12 +13,103 @@ Extended implementation by Alanse inc.
13
13
 
14
14
  ### 📊 Extended Google Workspace API Implementation
15
15
 
16
- **82 Total Tools** = 2 GDrive + **57 Google Sheets** + **23 Google Docs** operations
16
+ **130 Total Tools** = **21 Google Drive** + **57 Google Sheets** + **23 Google Docs** + **11 Gmail** + **18 Calendar** operations
17
17
 
18
18
  #### Tool Categories
19
19
 
20
- **Google Drive Operations (2 tools)**
21
- - Search & File Management
20
+ **Google Drive Operations (21 tools)**
21
+
22
+ #### Basic Operations (4 tools)
23
+
24
+ - Search Files (Advanced Query Support)
25
+ - Read File Contents
26
+ - List Files (Filter, Pagination, Sorting)
27
+ - Get File Metadata
28
+
29
+ #### File Operations (7 tools)
30
+
31
+ - Upload File
32
+ - Create File
33
+ - Delete File (Move to Trash)
34
+ - Copy File
35
+ - Move File
36
+ - Rename File
37
+ - Update File Content
38
+
39
+ #### Folder Management (3 tools)
40
+
41
+ - Create Folder
42
+ - List Folder Contents
43
+ - Move File to Folder
44
+
45
+ #### Permissions Management (4 tools)
46
+
47
+ - Share File (User/Group/Domain/Anyone)
48
+ - List File Permissions
49
+ - Update Permission Role
50
+ - Remove Permission
51
+
52
+ #### Advanced Operations (3 tools)
53
+
54
+ - Export File (Google Workspace Docs to PDF/DOCX/etc)
55
+ - List File Revisions
56
+ - Empty Trash
57
+
58
+ **Gmail Operations (11 tools)**
59
+
60
+ #### Basic Operations (4 tools)
61
+
62
+ - List Labels, Search Messages, Get Message, Get Thread
63
+
64
+ #### Label Management (2 tools)
65
+
66
+ - Modify Labels (Add/Remove), Manage Labels (Create/Update/Delete)
67
+
68
+ #### Send Operations (2 tools)
69
+
70
+ - Send Message (Plain/HTML with CC/BCC), Draft Message
71
+
72
+ #### Batch Operations (3 tools)
73
+
74
+ - Get Messages Batch, Get Threads Batch, Batch Modify Labels
75
+
76
+ **Google Calendar Operations (18 tools)**
77
+
78
+ #### Basic Event Operations (5 tools)
79
+
80
+ - List Events (Time range, search, pagination)
81
+ - Get Event (Full event details)
82
+ - Create Event (Title, time, location, attendees, Google Meet)
83
+ - Update Event (Modify any event properties)
84
+ - Delete Event (Remove with notifications)
85
+
86
+ #### Advanced Event Operations (3 tools)
87
+
88
+ - Quick Add Event (Natural language: "Meeting tomorrow at 2pm")
89
+ - List Event Instances (Recurring event occurrences)
90
+ - Move Event (Transfer between calendars)
91
+
92
+ #### Calendar List Operations (2 tools)
93
+
94
+ - List Calendars (User's calendar list with access roles)
95
+ - Get Calendar from List (Detailed calendar information)
96
+
97
+ #### Calendar Management (3 tools)
98
+
99
+ - Create Calendar (New secondary calendar)
100
+ - Get Calendar Metadata (Calendar details and settings)
101
+ - Update Calendar (Modify title, description, timezone)
102
+
103
+ #### Access Control (2 tools)
104
+
105
+ - List ACL Rules (View sharing settings)
106
+ - Add ACL Rule (Share calendar with users/groups/domains)
107
+
108
+ #### Utility Operations (3 tools)
109
+
110
+ - Get Color Palette (Available calendar/event colors)
111
+ - Query Free/Busy (Check availability across calendars)
112
+ - List Settings (User's Calendar preferences)
22
113
 
23
114
  **Google Docs Operations (23 tools)**
24
115
 
@@ -91,8 +182,11 @@ The server provides access to Google Drive files:
91
182
  1. [Create a new Google Cloud project](https://console.cloud.google.com/projectcreate)
92
183
  2. [Enable the Google Drive API](https://console.cloud.google.com/workspace-api/products)
93
184
  3. [Configure an OAuth consent screen](https://console.cloud.google.com/apis/credentials/consent) ("internal" is fine for testing)
94
- 4. Add OAuth scopes `https://www.googleapis.com/auth/drive.readonly`, `https://www.googleapis.com/auth/spreadsheets`
95
- 5. In order to allow interaction with sheets and docs you will also need to enable the [Google Sheets API](https://console.cloud.google.com/apis/api/sheets.googleapis.com/) and [Google Docs API](https://console.cloud.google.com/marketplace/product/google/docs.googleapis.com) in your workspaces Enabled API and Services section.
185
+ 4. Add OAuth scopes:
186
+ - `https://www.googleapis.com/auth/drive` (for Drive file operations - read & write)
187
+ - `https://www.googleapis.com/auth/spreadsheets` (for Sheets operations)
188
+ - `https://www.googleapis.com/auth/gmail.modify` (for Gmail operations)
189
+ 5. In order to allow interaction with sheets and docs you will also need to enable the [Google Sheets API](https://console.cloud.google.com/apis/api/sheets.googleapis.com/), [Google Docs API](https://console.cloud.google.com/marketplace/product/google/docs.googleapis.com), and [Gmail API](https://console.cloud.google.com/apis/api/gmail.googleapis.com) in your workspaces Enabled API and Services section.
96
190
  6. [Create an OAuth Client ID](https://console.cloud.google.com/apis/credentials/oauthclient) for application type "Desktop App"
97
191
  7. Download the JSON file of your client's OAuth keys
98
192
  8. Rename the key file to `gcp-oauth.keys.json` and place into the path you specify with `GWORKSPACE_CREDS_DIR` (i.e. `/Users/username/.config/mcp-google-workspace`)
@@ -152,27 +246,28 @@ Replace `<YOUR_CLIENT_ID>`, `<YOUR_CLIENT_SECRET>`, and `/path/to/config/directo
152
246
 
153
247
  ## 📈 Project Status
154
248
 
155
- ✅ **Phase 1-12 Complete** (2026-01-06)
156
- - **59 Tools Implemented** (57 Sheets + 2 GDrive)
157
- - **155 Tests** (100% Pass Rate)
249
+ ✅ **Complete** (2026-01-06)
250
+
251
+ - **112 Tools Implemented** (21 Drive + 57 Sheets + 23 Docs + 11 Gmail)
252
+ - **268+ Tests** (100% Pass Rate)
158
253
  - **Production Ready**
159
254
 
160
255
  ## 🗺️ Roadmap
161
256
 
162
- ### Current: Google Sheets & Drive
257
+ ### Completed
163
258
 
164
- - ✅ **Google Drive**: 2 tools (Search, Read)
259
+ - ✅ **Google Drive**: 21 tools (File operations, folder management, permissions, advanced features)
165
260
  - ✅ **Google Sheets**: 57 tools (Complete API coverage)
261
+ - ✅ **Google Docs**: 23 tools (Document creation, editing, formatting, elements)
262
+ - ✅ **Gmail**: 11 tools (Labels, search, send, batch operations)
166
263
 
167
264
  ### Coming Soon
168
265
 
169
- - 📝 **Google Docs**: Document creation, editing, formatting (~15-20 tools)
170
266
  - 📅 **Google Calendar**: Event management, scheduling (~10-15 tools)
171
267
  - 📋 **Google Forms**: Form creation, response management (~10-15 tools)
172
- - 📧 **Gmail**: Email operations, thread management (~10-15 tools)
173
268
  - 🎬 **Google Slides**: Presentation creation, editing (~15-20 tools)
174
269
 
175
- **Goal**: 100+ tools covering the entire Google Workspace ecosystem
270
+ **Goal**: 150+ tools covering the entire Google Workspace ecosystem
176
271
 
177
272
  ## 🛠️ Development
178
273
 
@@ -204,7 +299,7 @@ npm run watch
204
299
 
205
300
  ### Testing
206
301
 
207
- The project uses [Vitest](https://vitest.dev/) for testing:
302
+ The project uses [Vitest](https://vitest.dev/) for unit testing:
208
303
 
209
304
  ```bash
210
305
  # Run all tests
@@ -220,6 +315,40 @@ npm run test:ui
220
315
  npm run test:coverage
221
316
  ```
222
317
 
318
+ ### Interactive Testing with MCP Inspector
319
+
320
+ [MCP Inspector](https://modelcontextprotocol.io/docs/tools/inspector) is the official debugging tool for MCP servers. It provides an interactive interface to test all 130 tools in real-time.
321
+
322
+ **Launch Inspector:**
323
+
324
+ ```bash
325
+ npm run inspect
326
+ ```
327
+
328
+ This opens a browser interface where you can:
329
+
330
+ - 🔍 **Browse all 130 tools** - View complete tool schemas and descriptions
331
+ - ⚡ **Test tools interactively** - Execute any tool with custom parameters
332
+ - 📊 **Monitor responses** - See real-time responses and error messages
333
+ - 🐛 **Debug issues** - View server logs and notifications
334
+ - 🧪 **Test edge cases** - Try invalid inputs and concurrent operations
335
+
336
+ **Example workflow:**
337
+
338
+ 1. Start the inspector: `npm run inspect`
339
+ 2. Navigate to the **Tools** tab
340
+ 3. Select a tool (e.g., `calendar_create_event`)
341
+ 4. Fill in the parameters (summary, startTime, endTime)
342
+ 5. Click **Execute** to test the tool
343
+ 6. View the response and verify the behavior
344
+
345
+ **Recommended for:**
346
+
347
+ - Testing new Calendar API tools
348
+ - Verifying OAuth authentication flow
349
+ - Debugging API errors and edge cases
350
+ - Exploring available tools before integration
351
+
223
352
  ### Release Process
224
353
 
225
354
  This project uses [release-please](https://github.com/googleapis/release-please) for automated releases.
package/dist/auth.js CHANGED
@@ -6,6 +6,11 @@ export const SCOPES = [
6
6
  "https://www.googleapis.com/auth/drive.readonly",
7
7
  "https://www.googleapis.com/auth/spreadsheets",
8
8
  "https://www.googleapis.com/auth/documents",
9
+ "https://www.googleapis.com/auth/gmail.readonly",
10
+ "https://www.googleapis.com/auth/gmail.send",
11
+ "https://www.googleapis.com/auth/gmail.modify",
12
+ "https://www.googleapis.com/auth/gmail.labels",
13
+ "https://www.googleapis.com/auth/calendar",
9
14
  ];
10
15
  // Get credentials directory from environment variable or use default
11
16
  const CREDS_DIR = process.env.GWORKSPACE_CREDS_DIR ||
@@ -0,0 +1,197 @@
1
+ /**
2
+ * Google Calendar Helper Functions
3
+ *
4
+ * Utilities for Google Calendar API operations including:
5
+ * - Event formatting and display
6
+ * - Date/time parsing and formatting
7
+ * - Calendar URL generation
8
+ * - Free/busy data formatting
9
+ * - Attendee and recurrence handling
10
+ */
11
+ /**
12
+ * Format a single calendar event for display
13
+ */
14
+ export function formatEvent(event, calendarId = "primary") {
15
+ const lines = [];
16
+ // Event ID and status
17
+ lines.push(`Event ID: ${event.id}`);
18
+ if (event.status) {
19
+ lines.push(`Status: ${event.status}`);
20
+ }
21
+ // Summary (title)
22
+ if (event.summary) {
23
+ lines.push(`Title: ${event.summary}`);
24
+ }
25
+ // Date and time
26
+ const { startFormatted, endFormatted } = formatDateTime(event);
27
+ lines.push(`Start: ${startFormatted}`);
28
+ lines.push(`End: ${endFormatted}`);
29
+ // Location
30
+ if (event.location) {
31
+ lines.push(`Location: ${event.location}`);
32
+ }
33
+ // Description
34
+ if (event.description) {
35
+ const desc = event.description.length > 200
36
+ ? event.description.substring(0, 200) + "..."
37
+ : event.description;
38
+ lines.push(`Description: ${desc}`);
39
+ }
40
+ // Attendees
41
+ if (event.attendees && event.attendees.length > 0) {
42
+ lines.push(`Attendees (${event.attendees.length}):`);
43
+ event.attendees.forEach((attendee) => {
44
+ const status = attendee.responseStatus || "needsAction";
45
+ const organizer = attendee.organizer ? " (organizer)" : "";
46
+ lines.push(` - ${attendee.email} [${status}]${organizer}`);
47
+ });
48
+ }
49
+ // Recurrence
50
+ if (event.recurrence && event.recurrence.length > 0) {
51
+ lines.push(`Recurrence: ${event.recurrence.join(", ")}`);
52
+ }
53
+ // Web link
54
+ if (event.htmlLink) {
55
+ lines.push(`Link: ${event.htmlLink}`);
56
+ }
57
+ // Conference data (Google Meet, Zoom, etc.)
58
+ if (event.conferenceData?.entryPoints) {
59
+ const meetLink = event.conferenceData.entryPoints.find((ep) => ep.entryPointType === "video");
60
+ if (meetLink?.uri) {
61
+ lines.push(`Meeting Link: ${meetLink.uri}`);
62
+ }
63
+ }
64
+ return lines.join("\n");
65
+ }
66
+ /**
67
+ * Format a list of calendar events
68
+ */
69
+ export function formatEventList(events, calendarId = "primary") {
70
+ if (!events || events.length === 0) {
71
+ return "No events found.";
72
+ }
73
+ const lines = [];
74
+ lines.push(`Found ${events.length} event(s):\n`);
75
+ events.forEach((event, index) => {
76
+ lines.push(`[${index + 1}] ${event.summary || "(No title)"}`);
77
+ const { startFormatted } = formatDateTime(event);
78
+ lines.push(` Time: ${startFormatted}`);
79
+ if (event.location) {
80
+ lines.push(` Location: ${event.location}`);
81
+ }
82
+ if (event.attendees) {
83
+ lines.push(` Attendees: ${event.attendees.length}`);
84
+ }
85
+ lines.push(` ID: ${event.id}`);
86
+ lines.push("");
87
+ });
88
+ return lines.join("\n");
89
+ }
90
+ /**
91
+ * Format event date/time for display
92
+ */
93
+ export function formatDateTime(event) {
94
+ let startFormatted = "Unknown";
95
+ let endFormatted = "Unknown";
96
+ let isAllDay = false;
97
+ // Check if all-day event
98
+ if (event.start?.date) {
99
+ startFormatted = event.start.date;
100
+ endFormatted = event.end?.date || event.start.date;
101
+ isAllDay = true;
102
+ }
103
+ else if (event.start?.dateTime) {
104
+ const startDate = new Date(event.start.dateTime);
105
+ const endDate = event.end?.dateTime ? new Date(event.end.dateTime) : startDate;
106
+ const options = {
107
+ year: "numeric",
108
+ month: "short",
109
+ day: "numeric",
110
+ hour: "2-digit",
111
+ minute: "2-digit",
112
+ timeZoneName: "short",
113
+ };
114
+ startFormatted = startDate.toLocaleString("en-US", options);
115
+ endFormatted = endDate.toLocaleString("en-US", options);
116
+ }
117
+ return { startFormatted, endFormatted, isAllDay };
118
+ }
119
+ /**
120
+ * Generate Google Calendar web URL for an event
121
+ */
122
+ export function generateCalendarWebUrl(eventId, calendarId = "primary") {
123
+ const encodedCalendarId = encodeURIComponent(calendarId);
124
+ const encodedEventId = encodeURIComponent(eventId);
125
+ return `https://calendar.google.com/calendar/u/0/r/eventedit/${encodedEventId}?calendarId=${encodedCalendarId}`;
126
+ }
127
+ /**
128
+ * Format free/busy query response
129
+ */
130
+ export function formatFreeBusyResponse(response) {
131
+ const lines = [];
132
+ if (!response.calendars) {
133
+ return "No calendar data available.";
134
+ }
135
+ Object.entries(response.calendars).forEach(([calendarId, calendarData]) => {
136
+ lines.push(`\nCalendar: ${calendarId}`);
137
+ if (calendarData.errors && calendarData.errors.length > 0) {
138
+ lines.push(" Errors:");
139
+ calendarData.errors.forEach((error) => {
140
+ lines.push(` - ${error.reason}: ${error.domain}`);
141
+ });
142
+ }
143
+ if (calendarData.busy && calendarData.busy.length > 0) {
144
+ lines.push(` Busy periods (${calendarData.busy.length}):`);
145
+ calendarData.busy.forEach((period, index) => {
146
+ const start = period.start ? new Date(period.start).toLocaleString() : "Unknown";
147
+ const end = period.end ? new Date(period.end).toLocaleString() : "Unknown";
148
+ lines.push(` ${index + 1}. ${start} - ${end}`);
149
+ });
150
+ }
151
+ else {
152
+ lines.push(" No busy periods - calendar is free!");
153
+ }
154
+ });
155
+ return lines.join("\n");
156
+ }
157
+ /**
158
+ * Parse ISO 8601 datetime or date string to appropriate format for Calendar API
159
+ */
160
+ export function parseDateTime(dateTimeStr) {
161
+ // Check if it's a date-only string (YYYY-MM-DD)
162
+ if (/^\d{4}-\d{2}-\d{2}$/.test(dateTimeStr)) {
163
+ return { date: dateTimeStr };
164
+ }
165
+ // Otherwise, treat as datetime
166
+ return { dateTime: dateTimeStr };
167
+ }
168
+ /**
169
+ * Format attendee list for API request
170
+ */
171
+ export function formatAttendees(emails) {
172
+ return emails.map((email) => ({
173
+ email: email.trim(),
174
+ }));
175
+ }
176
+ /**
177
+ * Validate event time range
178
+ */
179
+ export function validateTimeRange(startTime, endTime) {
180
+ try {
181
+ const start = new Date(startTime);
182
+ const end = new Date(endTime);
183
+ if (isNaN(start.getTime())) {
184
+ return { valid: false, error: "Invalid start time format" };
185
+ }
186
+ if (isNaN(end.getTime())) {
187
+ return { valid: false, error: "Invalid end time format" };
188
+ }
189
+ if (end <= start) {
190
+ return { valid: false, error: "End time must be after start time" };
191
+ }
192
+ return { valid: true };
193
+ }
194
+ catch (error) {
195
+ return { valid: false, error: "Error parsing time range" };
196
+ }
197
+ }
@@ -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
+ }