@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.
- package/README.md +250 -17
- package/dist/auth.js +6 -0
- package/dist/index.js +1 -1
- package/dist/lib/calendar-helpers.js +197 -0
- package/dist/lib/document-id-resolver.js +76 -0
- package/dist/lib/drive-helpers.js +263 -0
- package/dist/lib/gmail-helpers.js +204 -0
- package/dist/lib/response-formatter.js +82 -0
- package/dist/lib/validation.js +112 -0
- package/dist/tools/calendar/acl/calendar_acl_insert.js +80 -0
- package/dist/tools/calendar/acl/calendar_acl_list.js +82 -0
- package/dist/tools/calendar/basic/calendar_create_event.js +113 -0
- package/dist/tools/calendar/basic/calendar_delete_event.js +52 -0
- package/dist/tools/calendar/basic/calendar_get_event.js +52 -0
- package/dist/tools/calendar/basic/calendar_list_events.js +86 -0
- package/dist/tools/calendar/basic/calendar_update_event.js +116 -0
- package/dist/tools/calendar/calendarlist/calendar_calendarlist_get.js +73 -0
- package/dist/tools/calendar/calendarlist/calendar_calendarlist_list.js +87 -0
- package/dist/tools/calendar/calendars/calendar_calendars_get.js +52 -0
- package/dist/tools/calendar/calendars/calendar_calendars_insert.js +66 -0
- package/dist/tools/calendar/calendars/calendar_calendars_update.js +85 -0
- package/dist/tools/calendar/colors/calendar_colors_get.js +46 -0
- package/dist/tools/calendar/events_advanced/calendar_events_instances.js +81 -0
- package/dist/tools/calendar/events_advanced/calendar_events_move.js +63 -0
- package/dist/tools/calendar/events_advanced/calendar_events_quickadd.js +52 -0
- package/dist/tools/calendar/freebusy/calendar_freebusy_query.js +69 -0
- package/dist/tools/calendar/settings/calendar_settings_list.js +81 -0
- package/dist/tools/docs/basic/gdocs_create.js +37 -0
- package/dist/tools/docs/basic/gdocs_get_metadata.js +45 -0
- package/dist/tools/docs/basic/gdocs_list_documents.js +59 -0
- package/dist/tools/docs/basic/gdocs_read.js +62 -0
- package/dist/tools/docs/content/gdocs_append_text.js +57 -0
- package/dist/tools/docs/content/gdocs_apply_style.js +86 -0
- package/dist/tools/docs/content/gdocs_create_heading.js +89 -0
- package/dist/tools/docs/content/gdocs_create_list.js +86 -0
- package/dist/tools/docs/content/gdocs_delete_text.js +64 -0
- package/dist/tools/docs/content/gdocs_format_text.js +137 -0
- package/dist/tools/docs/content/gdocs_insert_text.js +62 -0
- package/dist/tools/docs/content/gdocs_replace_text.js +64 -0
- package/dist/tools/docs/content/gdocs_set_alignment.js +76 -0
- package/dist/tools/docs/content/gdocs_update_text.js +78 -0
- package/dist/tools/docs/elements/gdocs_batch_update.js +108 -0
- package/dist/tools/docs/elements/gdocs_create_table.js +73 -0
- package/dist/tools/docs/elements/gdocs_export.js +62 -0
- package/dist/tools/docs/elements/gdocs_insert_image.js +96 -0
- package/dist/tools/docs/elements/gdocs_insert_link.js +77 -0
- package/dist/tools/docs/elements/gdocs_insert_page_break.js +55 -0
- package/dist/tools/docs/elements/gdocs_insert_toc.js +71 -0
- package/dist/tools/docs/elements/gdocs_merge_documents.js +104 -0
- package/dist/tools/docs/elements/gdocs_suggest_mode.js +41 -0
- package/dist/tools/drive/advanced/drive_empty_trash.js +56 -0
- package/dist/tools/drive/advanced/drive_export_file.js +158 -0
- package/dist/tools/drive/advanced/drive_list_revisions.js +80 -0
- package/dist/tools/drive/basic/drive_get_metadata.js +49 -0
- package/dist/tools/drive/basic/drive_list_files.js +76 -0
- package/dist/tools/drive/file/drive_copy_file.js +79 -0
- package/dist/tools/drive/file/drive_create_file.js +72 -0
- package/dist/tools/drive/file/drive_delete_file.js +48 -0
- package/dist/tools/drive/file/drive_move_file.js +79 -0
- package/dist/tools/drive/file/drive_rename_file.js +58 -0
- package/dist/tools/drive/file/drive_update_file.js +106 -0
- package/dist/tools/drive/file/drive_upload_file.js +80 -0
- package/dist/tools/drive/folder/drive_create_folder.js +67 -0
- package/dist/tools/drive/folder/drive_list_folder_contents.js +68 -0
- package/dist/tools/drive/folder/drive_move_to_folder.js +59 -0
- package/dist/tools/drive/permissions/drive_list_permissions.js +115 -0
- package/dist/tools/drive/permissions/drive_remove_permission.js +71 -0
- package/dist/tools/drive/permissions/drive_share_file.js +116 -0
- package/dist/tools/drive/permissions/drive_update_permission.js +79 -0
- package/dist/tools/gmail/basic/gmail_get_message.js +95 -0
- package/dist/tools/gmail/basic/gmail_get_thread.js +46 -0
- package/dist/tools/gmail/basic/gmail_list_labels.js +54 -0
- package/dist/tools/gmail/basic/gmail_search_messages.js +59 -0
- package/dist/tools/gmail/batch/gmail_batch_modify_labels.js +74 -0
- package/dist/tools/gmail/batch/gmail_get_messages_batch.js +120 -0
- package/dist/tools/gmail/batch/gmail_get_threads_batch.js +102 -0
- package/dist/tools/gmail/labels/gmail_manage_label.js +131 -0
- package/dist/tools/gmail/labels/gmail_modify_labels.js +65 -0
- package/dist/tools/gmail/send/gmail_draft_message.js +117 -0
- package/dist/tools/gmail/send/gmail_send_message.js +109 -0
- package/dist/tools/index.js +386 -3
- package/package.json +8 -3
- package/dist/tools/basic/gsheets_add_sheet.js +0 -65
- package/dist/tools/basic/gsheets_copy_sheet.js +0 -56
- package/dist/tools/basic/gsheets_copy_to.js +0 -113
- package/dist/tools/basic/gsheets_create_spreadsheet.js +0 -88
- package/dist/tools/basic/gsheets_delete_columns.js +0 -69
- package/dist/tools/basic/gsheets_delete_rows.js +0 -69
- package/dist/tools/basic/gsheets_delete_sheet.js +0 -56
- package/dist/tools/basic/gsheets_duplicate_sheet.js +0 -72
- package/dist/tools/basic/gsheets_insert_columns.js +0 -69
- package/dist/tools/basic/gsheets_insert_rows.js +0 -69
- package/dist/tools/basic/gsheets_list_sheets.js +0 -53
- package/dist/tools/basic/gsheets_read.js +0 -120
- package/dist/tools/basic/gsheets_rename_sheet.js +0 -64
- package/dist/tools/charts/gsheets_add_bubble.js +0 -176
- package/dist/tools/charts/gsheets_add_candlestick.js +0 -192
- package/dist/tools/charts/gsheets_add_chart.js +0 -162
- package/dist/tools/charts/gsheets_add_combo.js +0 -169
- package/dist/tools/charts/gsheets_add_histogram.js +0 -143
- package/dist/tools/charts/gsheets_add_org_chart.js +0 -160
- package/dist/tools/charts/gsheets_add_treemap.js +0 -177
- package/dist/tools/charts/gsheets_add_waterfall.js +0 -155
- package/dist/tools/charts/gsheets_delete_chart.js +0 -56
- package/dist/tools/charts/gsheets_update_chart.js +0 -118
- package/dist/tools/data/gsheets_append_data.js +0 -68
- package/dist/tools/data/gsheets_batch_clear.js +0 -53
- package/dist/tools/data/gsheets_batch_update.js +0 -81
- package/dist/tools/data/gsheets_clear_data.js +0 -53
- package/dist/tools/data/gsheets_create_filter.js +0 -81
- package/dist/tools/data/gsheets_find_replace.js +0 -124
- package/dist/tools/data/gsheets_set_data_validation.js +0 -153
- package/dist/tools/data/gsheets_sort_range.js +0 -102
- package/dist/tools/data/gsheets_update_cell.js +0 -44
- package/dist/tools/formatting/gsheets_auto_resize.js +0 -75
- package/dist/tools/formatting/gsheets_format_cells.js +0 -161
- package/dist/tools/formatting/gsheets_freeze_columns.js +0 -67
- package/dist/tools/formatting/gsheets_freeze_rows.js +0 -67
- package/dist/tools/formatting/gsheets_merge_cells.js +0 -85
- package/dist/tools/formatting/gsheets_set_number_format.js +0 -116
- package/dist/tools/formatting/gsheets_unmerge_cells.js +0 -79
- package/dist/tools/formatting/gsheets_update_borders.js +0 -212
- package/dist/tools/gdrive/gdrive_read_file.js +0 -77
- package/dist/tools/gdrive/gdrive_search.js +0 -71
- package/dist/tools/gdrive_read_file.js +0 -77
- package/dist/tools/gdrive_search.js +0 -71
- package/dist/tools/gsheets_add_bubble.js +0 -176
- package/dist/tools/gsheets_add_candlestick.js +0 -192
- package/dist/tools/gsheets_add_chart.js +0 -162
- package/dist/tools/gsheets_add_combo.js +0 -169
- package/dist/tools/gsheets_add_conditional_format.js +0 -175
- package/dist/tools/gsheets_add_histogram.js +0 -143
- package/dist/tools/gsheets_add_named_range.js +0 -87
- package/dist/tools/gsheets_add_org_chart.js +0 -160
- package/dist/tools/gsheets_add_protected_range.js +0 -127
- package/dist/tools/gsheets_add_sheet.js +0 -65
- package/dist/tools/gsheets_add_treemap.js +0 -177
- package/dist/tools/gsheets_add_waterfall.js +0 -155
- package/dist/tools/gsheets_append_data.js +0 -68
- package/dist/tools/gsheets_auto_resize.js +0 -75
- package/dist/tools/gsheets_batch_clear.js +0 -53
- package/dist/tools/gsheets_batch_update.js +0 -81
- package/dist/tools/gsheets_clear_data.js +0 -53
- package/dist/tools/gsheets_copy_sheet.js +0 -56
- package/dist/tools/gsheets_copy_to.js +0 -113
- package/dist/tools/gsheets_create_filter.js +0 -81
- package/dist/tools/gsheets_create_spreadsheet.js +0 -88
- package/dist/tools/gsheets_delete_chart.js +0 -56
- package/dist/tools/gsheets_delete_columns.js +0 -69
- package/dist/tools/gsheets_delete_named_range.js +0 -56
- package/dist/tools/gsheets_delete_protected_range.js +0 -56
- package/dist/tools/gsheets_delete_rows.js +0 -69
- package/dist/tools/gsheets_delete_sheet.js +0 -56
- package/dist/tools/gsheets_duplicate_sheet.js +0 -72
- package/dist/tools/gsheets_find_replace.js +0 -124
- package/dist/tools/gsheets_format_cells.js +0 -161
- package/dist/tools/gsheets_freeze_columns.js +0 -67
- package/dist/tools/gsheets_freeze_rows.js +0 -67
- package/dist/tools/gsheets_insert_columns.js +0 -69
- package/dist/tools/gsheets_insert_rows.js +0 -69
- package/dist/tools/gsheets_list_sheets.js +0 -53
- package/dist/tools/gsheets_merge_cells.js +0 -85
- package/dist/tools/gsheets_read.js +0 -120
- package/dist/tools/gsheets_rename_sheet.js +0 -64
- package/dist/tools/gsheets_set_data_validation.js +0 -153
- package/dist/tools/gsheets_set_number_format.js +0 -116
- package/dist/tools/gsheets_sort_range.js +0 -102
- package/dist/tools/gsheets_unmerge_cells.js +0 -79
- package/dist/tools/gsheets_update_borders.js +0 -212
- package/dist/tools/gsheets_update_cell.js +0 -44
- package/dist/tools/gsheets_update_chart.js +0 -118
- package/dist/tools/gsheets_update_named_range.js +0 -112
- package/dist/tools/gsheets_update_protected_range.js +0 -110
- package/dist/tools/protection/gsheets_add_conditional_format.js +0 -175
- package/dist/tools/protection/gsheets_add_named_range.js +0 -87
- package/dist/tools/protection/gsheets_add_protected_range.js +0 -127
- package/dist/tools/protection/gsheets_delete_named_range.js +0 -56
- package/dist/tools/protection/gsheets_delete_protected_range.js +0 -56
- package/dist/tools/protection/gsheets_update_named_range.js +0 -112
- package/dist/tools/protection/gsheets_update_protected_range.js +0 -110
- /package/dist/tools/drive/{drive_read_file.js → basic/drive_read_file.js} +0 -0
- /package/dist/tools/drive/{drive_search.js → basic/drive_search.js} +0 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { google } from "googleapis";
|
|
2
|
+
import { ResponseFormatter } from "../../../lib/response-formatter.js";
|
|
3
|
+
import { DocumentIdResolver } from "../../../lib/document-id-resolver.js";
|
|
4
|
+
import { Validator } from "../../../lib/validation.js";
|
|
5
|
+
export const schema = {
|
|
6
|
+
name: "gdocs_insert_image",
|
|
7
|
+
description: "Insert an image from a URL into a Google Document at a specific position.",
|
|
8
|
+
inputSchema: {
|
|
9
|
+
type: "object",
|
|
10
|
+
properties: {
|
|
11
|
+
documentId: {
|
|
12
|
+
type: "string",
|
|
13
|
+
description: "Document ID or full Google Docs URL",
|
|
14
|
+
},
|
|
15
|
+
imageUrl: {
|
|
16
|
+
type: "string",
|
|
17
|
+
description: "URL of the image to insert (must be publicly accessible)",
|
|
18
|
+
},
|
|
19
|
+
index: {
|
|
20
|
+
type: "number",
|
|
21
|
+
description: "Position to insert image (1-based)",
|
|
22
|
+
},
|
|
23
|
+
width: {
|
|
24
|
+
type: "number",
|
|
25
|
+
description: "Image width in points (optional)",
|
|
26
|
+
},
|
|
27
|
+
height: {
|
|
28
|
+
type: "number",
|
|
29
|
+
description: "Image height in points (optional)",
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
required: ["documentId", "imageUrl", "index"],
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
export async function insertImage(args) {
|
|
36
|
+
try {
|
|
37
|
+
// Validate inputs
|
|
38
|
+
if (!Validator.validateIndex(args.index)) {
|
|
39
|
+
throw new Error("Index must be a positive integer");
|
|
40
|
+
}
|
|
41
|
+
if (!Validator.validateUrl(args.imageUrl)) {
|
|
42
|
+
throw new Error("Invalid image URL");
|
|
43
|
+
}
|
|
44
|
+
if (args.width && !Validator.validatePositiveNumber(args.width)) {
|
|
45
|
+
throw new Error("Width must be a positive number");
|
|
46
|
+
}
|
|
47
|
+
if (args.height && !Validator.validatePositiveNumber(args.height)) {
|
|
48
|
+
throw new Error("Height must be a positive number");
|
|
49
|
+
}
|
|
50
|
+
// Resolve document ID from URL if needed
|
|
51
|
+
const docRef = DocumentIdResolver.resolve(args.documentId);
|
|
52
|
+
const documentId = docRef.id;
|
|
53
|
+
const docs = google.docs("v1");
|
|
54
|
+
// Build image properties
|
|
55
|
+
const imageProperties = {
|
|
56
|
+
contentUri: args.imageUrl,
|
|
57
|
+
};
|
|
58
|
+
if (args.width || args.height) {
|
|
59
|
+
imageProperties.sourceUri = args.imageUrl;
|
|
60
|
+
}
|
|
61
|
+
// Build object size if dimensions specified
|
|
62
|
+
let objectSize;
|
|
63
|
+
if (args.width || args.height) {
|
|
64
|
+
objectSize = {
|
|
65
|
+
width: args.width ? { magnitude: args.width, unit: "PT" } : undefined,
|
|
66
|
+
height: args.height ? { magnitude: args.height, unit: "PT" } : undefined,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
const response = await docs.documents.batchUpdate({
|
|
70
|
+
documentId,
|
|
71
|
+
requestBody: {
|
|
72
|
+
requests: [
|
|
73
|
+
{
|
|
74
|
+
insertInlineImage: {
|
|
75
|
+
uri: args.imageUrl,
|
|
76
|
+
location: {
|
|
77
|
+
index: args.index,
|
|
78
|
+
},
|
|
79
|
+
objectSize,
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
return ResponseFormatter.success({
|
|
86
|
+
documentId,
|
|
87
|
+
imageUrl: args.imageUrl,
|
|
88
|
+
insertedAt: args.index,
|
|
89
|
+
width: args.width,
|
|
90
|
+
height: args.height,
|
|
91
|
+
}, "Image inserted successfully");
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
return ResponseFormatter.error(error);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { google } from "googleapis";
|
|
2
|
+
import { ResponseFormatter } from "../../../lib/response-formatter.js";
|
|
3
|
+
import { DocumentIdResolver } from "../../../lib/document-id-resolver.js";
|
|
4
|
+
import { Validator } from "../../../lib/validation.js";
|
|
5
|
+
export const schema = {
|
|
6
|
+
name: "gdocs_insert_link",
|
|
7
|
+
description: "Insert a hyperlink on a text range in a Google Document.",
|
|
8
|
+
inputSchema: {
|
|
9
|
+
type: "object",
|
|
10
|
+
properties: {
|
|
11
|
+
documentId: {
|
|
12
|
+
type: "string",
|
|
13
|
+
description: "Document ID or full Google Docs URL",
|
|
14
|
+
},
|
|
15
|
+
startIndex: {
|
|
16
|
+
type: "number",
|
|
17
|
+
description: "Start position of text to link (1-based, inclusive)",
|
|
18
|
+
},
|
|
19
|
+
endIndex: {
|
|
20
|
+
type: "number",
|
|
21
|
+
description: "End position of text to link (1-based, exclusive)",
|
|
22
|
+
},
|
|
23
|
+
url: {
|
|
24
|
+
type: "string",
|
|
25
|
+
description: "URL to link to (must start with http:// or https://)",
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
required: ["documentId", "startIndex", "endIndex", "url"],
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
export async function insertLink(args) {
|
|
32
|
+
try {
|
|
33
|
+
// Validate inputs
|
|
34
|
+
if (!Validator.validateRange(args.startIndex, args.endIndex)) {
|
|
35
|
+
throw new Error("Invalid range: startIndex must be less than endIndex and both must be positive");
|
|
36
|
+
}
|
|
37
|
+
if (!Validator.validateUrl(args.url)) {
|
|
38
|
+
throw new Error("Invalid URL: must start with http:// or https://");
|
|
39
|
+
}
|
|
40
|
+
// Resolve document ID from URL if needed
|
|
41
|
+
const docRef = DocumentIdResolver.resolve(args.documentId);
|
|
42
|
+
const documentId = docRef.id;
|
|
43
|
+
const docs = google.docs("v1");
|
|
44
|
+
const response = await docs.documents.batchUpdate({
|
|
45
|
+
documentId,
|
|
46
|
+
requestBody: {
|
|
47
|
+
requests: [
|
|
48
|
+
{
|
|
49
|
+
updateTextStyle: {
|
|
50
|
+
textStyle: {
|
|
51
|
+
link: {
|
|
52
|
+
url: args.url,
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
fields: "link",
|
|
56
|
+
range: {
|
|
57
|
+
startIndex: args.startIndex,
|
|
58
|
+
endIndex: args.endIndex,
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
return ResponseFormatter.success({
|
|
66
|
+
documentId,
|
|
67
|
+
url: args.url,
|
|
68
|
+
range: {
|
|
69
|
+
startIndex: args.startIndex,
|
|
70
|
+
endIndex: args.endIndex,
|
|
71
|
+
},
|
|
72
|
+
}, "Hyperlink inserted successfully");
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
return ResponseFormatter.error(error);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { google } from "googleapis";
|
|
2
|
+
import { ResponseFormatter } from "../../../lib/response-formatter.js";
|
|
3
|
+
import { DocumentIdResolver } from "../../../lib/document-id-resolver.js";
|
|
4
|
+
import { Validator } from "../../../lib/validation.js";
|
|
5
|
+
export const schema = {
|
|
6
|
+
name: "gdocs_insert_page_break",
|
|
7
|
+
description: "Insert a page break at a specific position in a Google Document.",
|
|
8
|
+
inputSchema: {
|
|
9
|
+
type: "object",
|
|
10
|
+
properties: {
|
|
11
|
+
documentId: {
|
|
12
|
+
type: "string",
|
|
13
|
+
description: "Document ID or full Google Docs URL",
|
|
14
|
+
},
|
|
15
|
+
index: {
|
|
16
|
+
type: "number",
|
|
17
|
+
description: "Position to insert page break (1-based)",
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
required: ["documentId", "index"],
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
export async function insertPageBreak(args) {
|
|
24
|
+
try {
|
|
25
|
+
// Validate inputs
|
|
26
|
+
if (!Validator.validateIndex(args.index)) {
|
|
27
|
+
throw new Error("Index must be a positive integer");
|
|
28
|
+
}
|
|
29
|
+
// Resolve document ID from URL if needed
|
|
30
|
+
const docRef = DocumentIdResolver.resolve(args.documentId);
|
|
31
|
+
const documentId = docRef.id;
|
|
32
|
+
const docs = google.docs("v1");
|
|
33
|
+
const response = await docs.documents.batchUpdate({
|
|
34
|
+
documentId,
|
|
35
|
+
requestBody: {
|
|
36
|
+
requests: [
|
|
37
|
+
{
|
|
38
|
+
insertPageBreak: {
|
|
39
|
+
location: {
|
|
40
|
+
index: args.index,
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
return ResponseFormatter.success({
|
|
48
|
+
documentId,
|
|
49
|
+
insertedAt: args.index,
|
|
50
|
+
}, "Page break inserted successfully");
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
return ResponseFormatter.error(error);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { google } from "googleapis";
|
|
2
|
+
import { ResponseFormatter } from "../../../lib/response-formatter.js";
|
|
3
|
+
import { DocumentIdResolver } from "../../../lib/document-id-resolver.js";
|
|
4
|
+
import { Validator } from "../../../lib/validation.js";
|
|
5
|
+
export const schema = {
|
|
6
|
+
name: "gdocs_insert_toc",
|
|
7
|
+
description: "Insert a 'Table of Contents' heading at a specific position. NOTE: The Google Docs API doesn't support auto-generated TOC insertion. To insert an actual auto-updating TOC, use Insert > Table of contents in the Google Docs UI.",
|
|
8
|
+
inputSchema: {
|
|
9
|
+
type: "object",
|
|
10
|
+
properties: {
|
|
11
|
+
documentId: {
|
|
12
|
+
type: "string",
|
|
13
|
+
description: "Document ID or full Google Docs URL",
|
|
14
|
+
},
|
|
15
|
+
index: {
|
|
16
|
+
type: "number",
|
|
17
|
+
description: "Position to insert TOC heading (1-based)",
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
required: ["documentId", "index"],
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
export async function insertToc(args) {
|
|
24
|
+
try {
|
|
25
|
+
// Validate inputs
|
|
26
|
+
if (!Validator.validateIndex(args.index)) {
|
|
27
|
+
throw new Error("Index must be a positive integer");
|
|
28
|
+
}
|
|
29
|
+
// Resolve document ID from URL if needed
|
|
30
|
+
const docRef = DocumentIdResolver.resolve(args.documentId);
|
|
31
|
+
const documentId = docRef.id;
|
|
32
|
+
const docs = google.docs("v1");
|
|
33
|
+
// NOTE: Google Docs API doesn't support programmatic TOC insertion
|
|
34
|
+
// This is a workaround that inserts a styled "Table of Contents" heading
|
|
35
|
+
const response = await docs.documents.batchUpdate({
|
|
36
|
+
documentId,
|
|
37
|
+
requestBody: {
|
|
38
|
+
requests: [
|
|
39
|
+
{
|
|
40
|
+
insertText: {
|
|
41
|
+
location: {
|
|
42
|
+
index: args.index,
|
|
43
|
+
},
|
|
44
|
+
text: "Table of Contents\n",
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
updateParagraphStyle: {
|
|
49
|
+
range: {
|
|
50
|
+
startIndex: args.index,
|
|
51
|
+
endIndex: args.index + 19, // "Table of Contents\n".length
|
|
52
|
+
},
|
|
53
|
+
paragraphStyle: {
|
|
54
|
+
namedStyleType: "HEADING_1",
|
|
55
|
+
},
|
|
56
|
+
fields: "namedStyleType",
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
return ResponseFormatter.success({
|
|
63
|
+
documentId,
|
|
64
|
+
insertedAt: args.index,
|
|
65
|
+
note: "Inserted 'Table of Contents' heading. To insert an auto-updating TOC, use Insert > Table of contents in the Google Docs UI. The API doesn't support programmatic TOC generation.",
|
|
66
|
+
}, "Table of Contents heading inserted successfully");
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
return ResponseFormatter.error(error);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { google } from "googleapis";
|
|
2
|
+
import { ResponseFormatter } from "../../../lib/response-formatter.js";
|
|
3
|
+
import { DocumentIdResolver } from "../../../lib/document-id-resolver.js";
|
|
4
|
+
export const schema = {
|
|
5
|
+
name: "gdocs_merge_documents",
|
|
6
|
+
description: "Merge multiple Google Documents into one. Creates a new document or appends to an existing target document.",
|
|
7
|
+
inputSchema: {
|
|
8
|
+
type: "object",
|
|
9
|
+
properties: {
|
|
10
|
+
sourceDocumentIds: {
|
|
11
|
+
type: "array",
|
|
12
|
+
description: "Array of source document IDs or URLs to merge (in order)",
|
|
13
|
+
items: {
|
|
14
|
+
type: "string",
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
targetDocumentId: {
|
|
18
|
+
type: "string",
|
|
19
|
+
description: "Target document ID or URL to merge into. If not provided, creates a new document.",
|
|
20
|
+
},
|
|
21
|
+
title: {
|
|
22
|
+
type: "string",
|
|
23
|
+
description: "Title for the new merged document (only used if targetDocumentId is not provided)",
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
required: ["sourceDocumentIds"],
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
export async function mergeDocuments(args) {
|
|
30
|
+
try {
|
|
31
|
+
if (args.sourceDocumentIds.length === 0) {
|
|
32
|
+
throw new Error("At least one source document is required");
|
|
33
|
+
}
|
|
34
|
+
const docs = google.docs("v1");
|
|
35
|
+
// Resolve source document IDs
|
|
36
|
+
const sourceIds = args.sourceDocumentIds.map((id) => {
|
|
37
|
+
const ref = DocumentIdResolver.resolve(id);
|
|
38
|
+
return ref.id;
|
|
39
|
+
});
|
|
40
|
+
// Determine target document
|
|
41
|
+
let targetDocumentId;
|
|
42
|
+
if (args.targetDocumentId) {
|
|
43
|
+
const targetRef = DocumentIdResolver.resolve(args.targetDocumentId);
|
|
44
|
+
targetDocumentId = targetRef.id;
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
// Create a new document
|
|
48
|
+
const createResponse = await docs.documents.create({
|
|
49
|
+
requestBody: {
|
|
50
|
+
title: args.title || "Merged Document",
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
targetDocumentId = createResponse.data.documentId;
|
|
54
|
+
}
|
|
55
|
+
// Read each source document and append to target
|
|
56
|
+
for (const sourceId of sourceIds) {
|
|
57
|
+
const sourceDoc = await docs.documents.get({ documentId: sourceId });
|
|
58
|
+
// Extract text content from source
|
|
59
|
+
let textContent = "";
|
|
60
|
+
if (sourceDoc.data.body?.content) {
|
|
61
|
+
for (const element of sourceDoc.data.body.content) {
|
|
62
|
+
if (element.paragraph?.elements) {
|
|
63
|
+
for (const textElement of element.paragraph.elements) {
|
|
64
|
+
if (textElement.textRun?.content) {
|
|
65
|
+
textContent += textElement.textRun.content;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// Get target document end index
|
|
72
|
+
const targetDoc = await docs.documents.get({ documentId: targetDocumentId });
|
|
73
|
+
const endIndex = targetDoc.data.body?.content?.[targetDoc.data.body.content.length - 1]?.endIndex || 1;
|
|
74
|
+
// Append content to target
|
|
75
|
+
if (textContent) {
|
|
76
|
+
await docs.documents.batchUpdate({
|
|
77
|
+
documentId: targetDocumentId,
|
|
78
|
+
requestBody: {
|
|
79
|
+
requests: [
|
|
80
|
+
{
|
|
81
|
+
insertText: {
|
|
82
|
+
text: "\n" + textContent,
|
|
83
|
+
location: {
|
|
84
|
+
index: endIndex - 1,
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const targetUrl = `https://docs.google.com/document/d/${targetDocumentId}/edit`;
|
|
94
|
+
return ResponseFormatter.success({
|
|
95
|
+
targetDocumentId,
|
|
96
|
+
targetDocumentUrl: targetUrl,
|
|
97
|
+
mergedDocuments: sourceIds.length,
|
|
98
|
+
sourceDocumentIds: sourceIds,
|
|
99
|
+
}, `Successfully merged ${sourceIds.length} document(s)`);
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
return ResponseFormatter.error(error);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { google } from "googleapis";
|
|
2
|
+
import { ResponseFormatter } from "../../../lib/response-formatter.js";
|
|
3
|
+
import { DocumentIdResolver } from "../../../lib/document-id-resolver.js";
|
|
4
|
+
export const schema = {
|
|
5
|
+
name: "gdocs_suggest_mode",
|
|
6
|
+
description: "NOTE: This tool cannot programmatically control suggestion mode. The Google Docs API doesn't support enabling/disabling suggestion mode. To use suggestion mode, open the document in Google Docs UI and use the 'Editing/Suggesting/Viewing' dropdown in the top-right corner.",
|
|
7
|
+
inputSchema: {
|
|
8
|
+
type: "object",
|
|
9
|
+
properties: {
|
|
10
|
+
documentId: {
|
|
11
|
+
type: "string",
|
|
12
|
+
description: "Document ID or full Google Docs URL",
|
|
13
|
+
},
|
|
14
|
+
enabled: {
|
|
15
|
+
type: "boolean",
|
|
16
|
+
description: "true to enable suggestion mode, false to disable",
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
required: ["documentId", "enabled"],
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
export async function suggestMode(args) {
|
|
23
|
+
try {
|
|
24
|
+
// Resolve document ID from URL if needed
|
|
25
|
+
const docRef = DocumentIdResolver.resolve(args.documentId);
|
|
26
|
+
const documentId = docRef.id;
|
|
27
|
+
const docs = google.docs("v1");
|
|
28
|
+
// Verify document exists
|
|
29
|
+
await docs.documents.get({ documentId });
|
|
30
|
+
// Return informative error about API limitation
|
|
31
|
+
return ResponseFormatter.error(new Error(`Suggestion mode cannot be controlled programmatically via the Google Docs API. ` +
|
|
32
|
+
`To ${args.enabled ? "enable" : "disable"} suggestion mode, please:\n\n` +
|
|
33
|
+
`1. Open the document at: https://docs.google.com/document/d/${documentId}/edit\n` +
|
|
34
|
+
`2. Click the 'Editing' dropdown in the top-right corner\n` +
|
|
35
|
+
`3. Select '${args.enabled ? "Suggesting" : "Editing"}'\n\n` +
|
|
36
|
+
`This is a limitation of the Google Docs API, not this tool.`));
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
return ResponseFormatter.error(error);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -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
|
+
}
|