@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,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_events_quickadd",
6
+ description: "Create a calendar event from a simple text description using natural language processing. Examples: 'Meeting with John tomorrow at 2pm', 'Lunch at noon on Friday', 'Conference call next Monday 10-11am'. The system automatically parses the text to extract event details.",
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
+ text: {
15
+ type: "string",
16
+ description: "Natural language text describing the event (e.g., 'Dinner with Sarah tomorrow at 7pm')",
17
+ },
18
+ sendUpdates: {
19
+ type: "string",
20
+ description: "Whether to send event notifications: 'all', 'externalOnly', 'none'. Default: 'none'",
21
+ },
22
+ },
23
+ required: ["text"],
24
+ },
25
+ };
26
+ export async function quickAddEvent(args) {
27
+ try {
28
+ const calendar = google.calendar("v3");
29
+ const { calendarId = "primary", text, sendUpdates = "none", } = args;
30
+ const response = await calendar.events.quickAdd({
31
+ calendarId,
32
+ text,
33
+ sendUpdates,
34
+ });
35
+ const event = response.data;
36
+ let message = `✅ Event created via QuickAdd\n\n`;
37
+ message += `Input Text: "${text}"\n\n`;
38
+ message += formatEvent(event, calendarId);
39
+ return ResponseFormatter.success({
40
+ eventId: event.id,
41
+ calendarId,
42
+ summary: event.summary,
43
+ start: event.start,
44
+ end: event.end,
45
+ htmlLink: event.htmlLink,
46
+ inputText: text,
47
+ }, message);
48
+ }
49
+ catch (error) {
50
+ return ResponseFormatter.error(error);
51
+ }
52
+ }
@@ -0,0 +1,69 @@
1
+ import { google } from "googleapis";
2
+ import { ResponseFormatter } from "../../../lib/response-formatter.js";
3
+ import { formatFreeBusyResponse } from "../../../lib/calendar-helpers.js";
4
+ export const schema = {
5
+ name: "calendar_freebusy_query",
6
+ description: "Query free/busy information for one or more calendars within a specified time range. Returns busy time periods to help schedule meetings or check availability. Useful for finding meeting slots that work for multiple attendees.",
7
+ inputSchema: {
8
+ type: "object",
9
+ properties: {
10
+ timeMin: {
11
+ type: "string",
12
+ description: "Start of the time range to query (ISO 8601 format, e.g., '2024-01-01T00:00:00Z')",
13
+ },
14
+ timeMax: {
15
+ type: "string",
16
+ description: "End of the time range to query (ISO 8601 format)",
17
+ },
18
+ calendars: {
19
+ type: "array",
20
+ items: {
21
+ type: "string",
22
+ },
23
+ description: "List of calendar IDs to query (default: ['primary'])",
24
+ },
25
+ timeZone: {
26
+ type: "string",
27
+ description: "Time zone for the query (e.g., 'America/New_York', 'UTC'). Default: UTC",
28
+ },
29
+ },
30
+ required: ["timeMin", "timeMax"],
31
+ },
32
+ };
33
+ export async function queryFreeBusy(args) {
34
+ try {
35
+ const calendar = google.calendar("v3");
36
+ const { timeMin, timeMax, calendars = ["primary"], timeZone = "UTC", } = args;
37
+ const response = await calendar.freebusy.query({
38
+ requestBody: {
39
+ timeMin,
40
+ timeMax,
41
+ timeZone,
42
+ items: calendars.map((id) => ({ id })),
43
+ },
44
+ });
45
+ const freeBusyData = response.data;
46
+ if (!freeBusyData) {
47
+ return ResponseFormatter.error(new Error("Failed to query free/busy information"));
48
+ }
49
+ const formattedOutput = formatFreeBusyResponse(freeBusyData);
50
+ // Calculate total busy periods across all calendars
51
+ let totalBusyPeriods = 0;
52
+ if (freeBusyData.calendars) {
53
+ Object.values(freeBusyData.calendars).forEach((cal) => {
54
+ if (cal.busy) {
55
+ totalBusyPeriods += cal.busy.length;
56
+ }
57
+ });
58
+ }
59
+ return ResponseFormatter.success({
60
+ timeMin: freeBusyData.timeMin,
61
+ timeMax: freeBusyData.timeMax,
62
+ calendars: freeBusyData.calendars,
63
+ totalBusyPeriods,
64
+ }, `Free/Busy query results:\n\n${formattedOutput}`);
65
+ }
66
+ catch (error) {
67
+ return ResponseFormatter.error(error);
68
+ }
69
+ }
@@ -0,0 +1,81 @@
1
+ import { google } from "googleapis";
2
+ import { ResponseFormatter } from "../../../lib/response-formatter.js";
3
+ export const schema = {
4
+ name: "calendar_settings_list",
5
+ description: "List all user settings for the authenticated user's Google Calendar. Returns configuration settings such as timezone, date format, country, language, and other Calendar preferences. Useful for understanding user's Calendar configuration.",
6
+ inputSchema: {
7
+ type: "object",
8
+ properties: {
9
+ maxResults: {
10
+ type: "number",
11
+ description: "Maximum number of settings to return per page (default: 100, max: 250)",
12
+ },
13
+ pageToken: {
14
+ type: "string",
15
+ description: "Token for accessing subsequent result pages",
16
+ },
17
+ },
18
+ required: [],
19
+ },
20
+ };
21
+ export async function listSettings(args = {}) {
22
+ try {
23
+ const calendar = google.calendar("v3");
24
+ const { maxResults, pageToken, } = args;
25
+ const response = await calendar.settings.list({
26
+ maxResults,
27
+ pageToken,
28
+ });
29
+ const settings = response.data.items || [];
30
+ const totalSettings = settings.length;
31
+ if (totalSettings === 0) {
32
+ return ResponseFormatter.success({ settings: [], count: 0 }, "No settings found.");
33
+ }
34
+ let message = `⚙️ Calendar Settings (${totalSettings} setting${totalSettings > 1 ? "s" : ""})\n\n`;
35
+ // Group settings by category for better readability
36
+ const categorizedSettings = {
37
+ "General": [],
38
+ "Time & Date": [],
39
+ "Locale": [],
40
+ "Other": [],
41
+ };
42
+ settings.forEach((setting) => {
43
+ const id = setting.id || "";
44
+ if (id.includes("timezone") || id.includes("time") || id.includes("date") || id.includes("hour")) {
45
+ categorizedSettings["Time & Date"].push(setting);
46
+ }
47
+ else if (id.includes("locale") || id.includes("language") || id.includes("country")) {
48
+ categorizedSettings["Locale"].push(setting);
49
+ }
50
+ else if (id.includes("format") || id.includes("weekStart") || id.includes("show")) {
51
+ categorizedSettings["General"].push(setting);
52
+ }
53
+ else {
54
+ categorizedSettings["Other"].push(setting);
55
+ }
56
+ });
57
+ // Display settings by category
58
+ Object.entries(categorizedSettings).forEach(([category, items]) => {
59
+ if (items.length > 0) {
60
+ message += `\n${category}:\n`;
61
+ items.forEach((setting) => {
62
+ message += ` ${setting.id}: ${setting.value}\n`;
63
+ });
64
+ }
65
+ });
66
+ if (response.data.nextPageToken) {
67
+ message += `\n\n📄 Next Page Token: ${response.data.nextPageToken}`;
68
+ }
69
+ return ResponseFormatter.success({
70
+ settings: settings.map((s) => ({
71
+ id: s.id,
72
+ value: s.value,
73
+ })),
74
+ count: totalSettings,
75
+ nextPageToken: response.data.nextPageToken || null,
76
+ }, message);
77
+ }
78
+ catch (error) {
79
+ return ResponseFormatter.error(error);
80
+ }
81
+ }
@@ -0,0 +1,56 @@
1
+ import { google } from "googleapis";
2
+ import { ResponseFormatter } from "../../../lib/response-formatter.js";
3
+ export const schema = {
4
+ name: "drive_empty_trash",
5
+ description: "Permanently delete all files in the trash for the authenticated user. This operation cannot be undone.",
6
+ inputSchema: {
7
+ type: "object",
8
+ properties: {},
9
+ required: [],
10
+ },
11
+ };
12
+ export async function emptyTrash(args) {
13
+ try {
14
+ const drive = google.drive("v3");
15
+ // First, count items in trash for confirmation message
16
+ let trashedItemsCount = 0;
17
+ let nextPageToken;
18
+ try {
19
+ // Get count of items in trash
20
+ const countResponse = await drive.files.list({
21
+ q: "trashed=true",
22
+ pageSize: 1,
23
+ fields: "nextPageToken",
24
+ });
25
+ // Try to get actual count by iterating through all pages
26
+ let hasMore = true;
27
+ nextPageToken = countResponse.data.nextPageToken || undefined;
28
+ // Estimate count from first response
29
+ if (countResponse.data.files && countResponse.data.files.length > 0) {
30
+ trashedItemsCount = countResponse.data.files.length;
31
+ }
32
+ }
33
+ catch {
34
+ // If count fails, proceed with emptying trash anyway
35
+ trashedItemsCount = 0;
36
+ }
37
+ // Empty the trash
38
+ await drive.files.emptyTrash({});
39
+ let output = `🗑️ Trash Emptied Successfully!\n`;
40
+ output += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`;
41
+ output += `✅ All files in trash have been permanently deleted.\n`;
42
+ output += `⚠️ This action cannot be undone.\n`;
43
+ if (trashedItemsCount > 0) {
44
+ output += `📊 Estimated items deleted: ${trashedItemsCount}+\n`;
45
+ }
46
+ return ResponseFormatter.success({
47
+ status: "success",
48
+ message: "Trash emptied successfully",
49
+ timestamp: new Date().toISOString(),
50
+ warning: "This action cannot be undone. Deleted files are permanently removed.",
51
+ }, output);
52
+ }
53
+ catch (error) {
54
+ return ResponseFormatter.error(error);
55
+ }
56
+ }
@@ -0,0 +1,158 @@
1
+ import { google } from "googleapis";
2
+ import { ResponseFormatter } from "../../../lib/response-formatter.js";
3
+ import { convertMimeType, isGoogleWorkspaceDocument, } from "../../../lib/drive-helpers.js";
4
+ export const schema = {
5
+ name: "drive_export_file",
6
+ description: "Export a Google Workspace document (Docs, Sheets, Slides, Drawing) to a different format. Only works with Google Workspace files.",
7
+ inputSchema: {
8
+ type: "object",
9
+ properties: {
10
+ fileId: {
11
+ type: "string",
12
+ description: "ID of the file to export",
13
+ },
14
+ mimeType: {
15
+ type: "string",
16
+ description: "Target MIME type for export. Optional - uses default if not specified. Examples: application/pdf, text/csv, text/plain, etc.",
17
+ optional: true,
18
+ },
19
+ format: {
20
+ type: "string",
21
+ description: "Export format name (shorthand). For Docs: pdf, docx, html, txt, odt, rtf, epub, markdown. For Sheets: csv, xlsx, ods, tsv, html. For Slides: pdf, pptx, odp, txt. For Drawing: png, jpeg, svg, pdf.",
22
+ optional: true,
23
+ },
24
+ },
25
+ required: ["fileId"],
26
+ },
27
+ };
28
+ export async function exportFile(args) {
29
+ try {
30
+ const drive = google.drive("v3");
31
+ const { fileId, mimeType: customMimeType, format } = args;
32
+ // Get file metadata to check type
33
+ const fileResponse = await drive.files.get({
34
+ fileId,
35
+ fields: "id, name, mimeType, size",
36
+ });
37
+ const file = fileResponse.data;
38
+ const sourceMimeType = file.mimeType || "";
39
+ // Check if it's a Google Workspace document
40
+ if (!isGoogleWorkspaceDocument(sourceMimeType)) {
41
+ return ResponseFormatter.error(`File is not a Google Workspace document. MIME type: ${sourceMimeType}. Only Google Docs, Sheets, Slides, and Drawings can be exported.`);
42
+ }
43
+ // Determine target MIME type
44
+ let targetMimeType;
45
+ if (customMimeType) {
46
+ targetMimeType = customMimeType;
47
+ }
48
+ else if (format) {
49
+ try {
50
+ targetMimeType = convertMimeType(sourceMimeType, format);
51
+ }
52
+ catch (error) {
53
+ return ResponseFormatter.error(error.message);
54
+ }
55
+ }
56
+ else {
57
+ // Use default format
58
+ try {
59
+ targetMimeType = convertMimeType(sourceMimeType);
60
+ }
61
+ catch (error) {
62
+ return ResponseFormatter.error(error.message);
63
+ }
64
+ }
65
+ // Get export MIME type label
66
+ const formatLabel = getFormatLabel(targetMimeType);
67
+ // Export the file
68
+ const exportResponse = await drive.files.export({
69
+ fileId,
70
+ mimeType: targetMimeType,
71
+ }, { responseType: "arraybuffer" });
72
+ const fileBuffer = Buffer.from(exportResponse.data);
73
+ const base64Content = fileBuffer.toString("base64");
74
+ const fileName = `${file.name || "export"}.${getFileExtension(targetMimeType)}`;
75
+ let output = `✅ File exported successfully!\n\n`;
76
+ output += `📄 File Name: ${file.name}\n`;
77
+ output += `📊 Source Format: ${getDocumentTypeLabel(sourceMimeType)}\n`;
78
+ output += `📥 Exported Format: ${formatLabel}\n`;
79
+ output += `💾 File Size: ${(fileBuffer.length / 1024).toFixed(2)} KB\n`;
80
+ output += `📁 Export File Name: ${fileName}\n`;
81
+ return ResponseFormatter.success({
82
+ fileId,
83
+ fileName: file.name,
84
+ exportFileName: fileName,
85
+ sourceFormat: sourceMimeType,
86
+ targetFormat: targetMimeType,
87
+ fileSizeBytes: fileBuffer.length,
88
+ fileSizeKB: (fileBuffer.length / 1024).toFixed(2),
89
+ content: base64Content,
90
+ contentEncoding: "base64",
91
+ }, output);
92
+ }
93
+ catch (error) {
94
+ return ResponseFormatter.error(error);
95
+ }
96
+ }
97
+ /**
98
+ * Get file extension based on MIME type
99
+ */
100
+ function getFileExtension(mimeType) {
101
+ const extensions = {
102
+ "application/pdf": "pdf",
103
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document": "docx",
104
+ "text/html": "html",
105
+ "text/plain": "txt",
106
+ "application/vnd.oasis.opendocument.text": "odt",
107
+ "application/rtf": "rtf",
108
+ "application/epub+zip": "epub",
109
+ "text/markdown": "md",
110
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": "xlsx",
111
+ "text/csv": "csv",
112
+ "application/vnd.oasis.opendocument.spreadsheet": "ods",
113
+ "text/tab-separated-values": "tsv",
114
+ "application/vnd.openxmlformats-officedocument.presentationml.presentation": "pptx",
115
+ "application/vnd.oasis.opendocument.presentation": "odp",
116
+ "image/png": "png",
117
+ "image/jpeg": "jpg",
118
+ "image/svg+xml": "svg",
119
+ };
120
+ return extensions[mimeType] || "bin";
121
+ }
122
+ /**
123
+ * Get format label for display
124
+ */
125
+ function getFormatLabel(mimeType) {
126
+ const labels = {
127
+ "application/pdf": "PDF Document",
128
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document": "Microsoft Word",
129
+ "text/html": "HTML",
130
+ "text/plain": "Plain Text",
131
+ "application/vnd.oasis.opendocument.text": "OpenDocument Text",
132
+ "application/rtf": "Rich Text Format",
133
+ "application/epub+zip": "EPUB eBook",
134
+ "text/markdown": "Markdown",
135
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": "Microsoft Excel",
136
+ "text/csv": "CSV (Comma Separated Values)",
137
+ "application/vnd.oasis.opendocument.spreadsheet": "OpenDocument Spreadsheet",
138
+ "text/tab-separated-values": "TSV (Tab Separated Values)",
139
+ "application/vnd.openxmlformats-officedocument.presentationml.presentation": "Microsoft PowerPoint",
140
+ "application/vnd.oasis.opendocument.presentation": "OpenDocument Presentation",
141
+ "image/png": "PNG Image",
142
+ "image/jpeg": "JPEG Image",
143
+ "image/svg+xml": "SVG Image",
144
+ };
145
+ return labels[mimeType] || mimeType;
146
+ }
147
+ /**
148
+ * Get document type label
149
+ */
150
+ function getDocumentTypeLabel(mimeType) {
151
+ const labels = {
152
+ "application/vnd.google-apps.document": "Google Docs",
153
+ "application/vnd.google-apps.spreadsheet": "Google Sheets",
154
+ "application/vnd.google-apps.presentation": "Google Slides",
155
+ "application/vnd.google-apps.drawing": "Google Drawing",
156
+ };
157
+ return labels[mimeType] || "Google Workspace Document";
158
+ }
@@ -0,0 +1,80 @@
1
+ import { google } from "googleapis";
2
+ import { ResponseFormatter } from "../../../lib/response-formatter.js";
3
+ import { formatRevisionList, } from "../../../lib/drive-helpers.js";
4
+ export const schema = {
5
+ name: "drive_list_revisions",
6
+ description: "List all revisions of a file in Google Drive. Shows revision history with timestamps and modification details. Supports pagination.",
7
+ inputSchema: {
8
+ type: "object",
9
+ properties: {
10
+ fileId: {
11
+ type: "string",
12
+ description: "ID of the file to list revisions for",
13
+ },
14
+ pageSize: {
15
+ type: "number",
16
+ description: "Maximum number of revisions to return per page (default: 10, max: 1000)",
17
+ optional: true,
18
+ },
19
+ pageToken: {
20
+ type: "string",
21
+ description: "Token for pagination to get the next page of results",
22
+ optional: true,
23
+ },
24
+ },
25
+ required: ["fileId"],
26
+ },
27
+ };
28
+ export async function listRevisions(args) {
29
+ try {
30
+ const drive = google.drive("v3");
31
+ const { fileId, pageSize = 10, pageToken } = args;
32
+ // Get file metadata
33
+ const fileResponse = await drive.files.get({
34
+ fileId,
35
+ fields: "id, name, mimeType, size, modifiedTime, createdTime",
36
+ });
37
+ const file = fileResponse.data;
38
+ // List revisions
39
+ const revisionsResponse = await drive.revisions.list({
40
+ fileId,
41
+ pageSize: Math.min(pageSize, 1000), // Cap at 1000
42
+ pageToken: pageToken || undefined,
43
+ fields: "revisions(id, modifiedTime, lastModifyingUser, size, keepForever, originalFilename, mimeType), nextPageToken",
44
+ });
45
+ const revisions = revisionsResponse.data.revisions || [];
46
+ const nextPageToken = revisionsResponse.data.nextPageToken;
47
+ // Format output
48
+ let output = `📄 Revision History for "${file.name}"\n`;
49
+ output += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`;
50
+ output += `Total Revisions: ${revisions.length}\n`;
51
+ if (nextPageToken) {
52
+ output += `(More revisions available - use nextPageToken for pagination)\n`;
53
+ }
54
+ output += `\n${formatRevisionList(revisions)}\n`;
55
+ // Format detailed output for each revision
56
+ let detailedRevisions = revisions.map((revision, index) => ({
57
+ index: index + 1,
58
+ revisionId: revision.id || "",
59
+ modifiedTime: revision.modifiedTime || "Unknown",
60
+ modifiedBy: revision.lastModifyingUser?.displayName || "Unknown",
61
+ modifiedByEmail: revision.lastModifyingUser?.emailAddress || "Unknown",
62
+ size: revision.size ? parseInt(revision.size) : 0,
63
+ mimeType: revision.mimeType || "Unknown",
64
+ keepForever: revision.keepForever || false,
65
+ originalFilename: revision.originalFilename || file.name,
66
+ }));
67
+ return ResponseFormatter.success({
68
+ fileId,
69
+ fileName: file.name,
70
+ totalRevisions: revisions.length,
71
+ revisions: detailedRevisions,
72
+ nextPageToken: nextPageToken || null,
73
+ hasMoreResults: !!nextPageToken,
74
+ pageSize,
75
+ }, output);
76
+ }
77
+ catch (error) {
78
+ return ResponseFormatter.error(error);
79
+ }
80
+ }
@@ -0,0 +1,49 @@
1
+ import { google } from "googleapis";
2
+ import { ResponseFormatter } from "../../../lib/response-formatter.js";
3
+ import { formatFileMetadata } from "../../../lib/drive-helpers.js";
4
+ export const schema = {
5
+ name: "drive_get_metadata",
6
+ description: "Get detailed metadata for a specific file in Google Drive. Returns file ID, name, MIME type, size, created/modified times, sharing settings, and more.",
7
+ inputSchema: {
8
+ type: "object",
9
+ properties: {
10
+ fileId: {
11
+ type: "string",
12
+ description: "ID of the file to get metadata for",
13
+ },
14
+ fields: {
15
+ type: "string",
16
+ description: "Comma-separated list of fields to include (default: all common fields)",
17
+ optional: true,
18
+ },
19
+ },
20
+ required: ["fileId"],
21
+ },
22
+ };
23
+ export async function getMetadata(args) {
24
+ try {
25
+ const drive = google.drive("v3");
26
+ const { fileId, fields } = args;
27
+ const defaultFields = "id, name, mimeType, size, createdTime, modifiedTime, modifiedByMe, owners, lastModifyingUser, shared, sharingUser, permissions, parents, webViewLink, webContentLink, iconLink, thumbnailLink, description, starred, trashed, explicitlyTrashed, properties, appProperties, spaces, version, teamDriveId, driveId, hasAugmentedPermissions, hasVersions, headRevisionId, copyRequiresWriterPermission, writersCanShare, folderColorRgb, originalFilename, fullFileExtension, fileExtension, md5Checksum, sha1Checksum, sha256Checksum";
28
+ const response = await drive.files.get({
29
+ fileId,
30
+ fields: fields || defaultFields,
31
+ supportsAllDrives: true,
32
+ });
33
+ const file = response.data;
34
+ const output = formatFileMetadata(file, true);
35
+ return ResponseFormatter.success({
36
+ fileId: file.id,
37
+ name: file.name,
38
+ mimeType: file.mimeType,
39
+ size: file.size,
40
+ createdTime: file.createdTime,
41
+ modifiedTime: file.modifiedTime,
42
+ webViewLink: file.webViewLink,
43
+ metadata: file,
44
+ }, output);
45
+ }
46
+ catch (error) {
47
+ return ResponseFormatter.error(error);
48
+ }
49
+ }
@@ -0,0 +1,76 @@
1
+ import { google } from "googleapis";
2
+ import { ResponseFormatter } from "../../../lib/response-formatter.js";
3
+ import { formatFileList } from "../../../lib/drive-helpers.js";
4
+ export const schema = {
5
+ name: "drive_list_files",
6
+ description: "List files in Google Drive. Supports filtering by query, folder, pagination, and sorting. Returns file IDs, names, types, and sizes.",
7
+ inputSchema: {
8
+ type: "object",
9
+ properties: {
10
+ query: {
11
+ type: "string",
12
+ description: "Search query using Google Drive query syntax (e.g., \"name contains 'report'\")",
13
+ optional: true,
14
+ },
15
+ folderId: {
16
+ type: "string",
17
+ description: "Folder ID to list files from (limits search to specific folder)",
18
+ optional: true,
19
+ },
20
+ pageSize: {
21
+ type: "number",
22
+ description: "Number of files to return per page (max 1000, default 100)",
23
+ optional: true,
24
+ },
25
+ pageToken: {
26
+ type: "string",
27
+ description: "Page token for pagination",
28
+ optional: true,
29
+ },
30
+ orderBy: {
31
+ type: "string",
32
+ description: "Sort order (e.g., 'name', 'modifiedTime desc', 'createdTime')",
33
+ optional: true,
34
+ },
35
+ },
36
+ required: [],
37
+ },
38
+ };
39
+ export async function listFiles(args) {
40
+ try {
41
+ const drive = google.drive("v3");
42
+ const { query, folderId, pageSize = 100, pageToken, orderBy } = args;
43
+ // Build query
44
+ let q = "trashed = false";
45
+ if (query) {
46
+ q += ` and (${query})`;
47
+ }
48
+ if (folderId) {
49
+ q += ` and '${folderId}' in parents`;
50
+ }
51
+ const response = await drive.files.list({
52
+ q,
53
+ pageSize: Math.min(pageSize, 1000),
54
+ pageToken,
55
+ orderBy: orderBy || "modifiedTime desc",
56
+ fields: "nextPageToken, files(id, name, mimeType, size, modifiedTime, createdTime, webViewLink, parents, iconLink)",
57
+ });
58
+ const files = response.data.files || [];
59
+ const nextPageToken = response.data.nextPageToken;
60
+ const output = formatFileList(files, nextPageToken);
61
+ return ResponseFormatter.success({
62
+ count: files.length,
63
+ files: files.map((f) => ({
64
+ id: f.id,
65
+ name: f.name,
66
+ mimeType: f.mimeType,
67
+ size: f.size,
68
+ modifiedTime: f.modifiedTime,
69
+ })),
70
+ nextPageToken,
71
+ }, output);
72
+ }
73
+ catch (error) {
74
+ return ResponseFormatter.error(error);
75
+ }
76
+ }