@alanse/mcp-server-google-workspace 0.2.1 → 1.0.0
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 +109 -5
- package/dist/auth.js +1 -0
- package/dist/index.js +1 -1
- package/dist/lib/document-id-resolver.js +76 -0
- package/dist/lib/response-formatter.js +82 -0
- package/dist/lib/validation.js +112 -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/index.js +119 -0
- package/package.json +1 -1
|
@@ -0,0 +1,78 @@
|
|
|
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_update_text",
|
|
7
|
+
description: "Update (replace) text in a specific range in a Google Document. Indices are 1-based.",
|
|
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 replace (1-based, inclusive)",
|
|
18
|
+
},
|
|
19
|
+
endIndex: {
|
|
20
|
+
type: "number",
|
|
21
|
+
description: "End position of text to replace (1-based, exclusive)",
|
|
22
|
+
},
|
|
23
|
+
text: {
|
|
24
|
+
type: "string",
|
|
25
|
+
description: "New text to replace the range with",
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
required: ["documentId", "startIndex", "endIndex", "text"],
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
export async function updateText(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
|
+
// Resolve document ID from URL if needed
|
|
38
|
+
const docRef = DocumentIdResolver.resolve(args.documentId);
|
|
39
|
+
const documentId = docRef.id;
|
|
40
|
+
const docs = google.docs("v1");
|
|
41
|
+
// First delete the range, then insert new text
|
|
42
|
+
const response = await docs.documents.batchUpdate({
|
|
43
|
+
documentId,
|
|
44
|
+
requestBody: {
|
|
45
|
+
requests: [
|
|
46
|
+
{
|
|
47
|
+
deleteContentRange: {
|
|
48
|
+
range: {
|
|
49
|
+
startIndex: args.startIndex,
|
|
50
|
+
endIndex: args.endIndex,
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
insertText: {
|
|
56
|
+
text: args.text,
|
|
57
|
+
location: {
|
|
58
|
+
index: args.startIndex,
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
return ResponseFormatter.success({
|
|
66
|
+
documentId,
|
|
67
|
+
updatedRange: {
|
|
68
|
+
startIndex: args.startIndex,
|
|
69
|
+
endIndex: args.startIndex + args.text.length,
|
|
70
|
+
},
|
|
71
|
+
newText: args.text,
|
|
72
|
+
replacedLength: args.endIndex - args.startIndex,
|
|
73
|
+
}, "Text updated successfully");
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
return ResponseFormatter.error(error);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
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_batch_update",
|
|
6
|
+
description: "Execute multiple update operations on a Google Document in a single API call. Requests are executed in order.",
|
|
7
|
+
inputSchema: {
|
|
8
|
+
type: "object",
|
|
9
|
+
properties: {
|
|
10
|
+
documentId: {
|
|
11
|
+
type: "string",
|
|
12
|
+
description: "Document ID or full Google Docs URL",
|
|
13
|
+
},
|
|
14
|
+
requests: {
|
|
15
|
+
type: "array",
|
|
16
|
+
description: "Array of update requests to execute",
|
|
17
|
+
items: {
|
|
18
|
+
type: "object",
|
|
19
|
+
properties: {
|
|
20
|
+
type: {
|
|
21
|
+
type: "string",
|
|
22
|
+
description: "Type of operation",
|
|
23
|
+
enum: [
|
|
24
|
+
"insertText",
|
|
25
|
+
"deleteContentRange",
|
|
26
|
+
"updateTextStyle",
|
|
27
|
+
"updateParagraphStyle",
|
|
28
|
+
"createParagraphBullets",
|
|
29
|
+
"insertTable",
|
|
30
|
+
"insertInlineImage",
|
|
31
|
+
"insertPageBreak",
|
|
32
|
+
"createHeader",
|
|
33
|
+
"createFooter",
|
|
34
|
+
],
|
|
35
|
+
},
|
|
36
|
+
params: {
|
|
37
|
+
type: "object",
|
|
38
|
+
description: "Parameters for the operation (structure depends on type)",
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
required: ["type", "params"],
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
required: ["documentId", "requests"],
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
export async function batchUpdate(args) {
|
|
49
|
+
try {
|
|
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
|
+
// Convert our simplified request format to Google Docs API format
|
|
55
|
+
const requests = args.requests.map((req) => {
|
|
56
|
+
const apiRequest = {};
|
|
57
|
+
switch (req.type) {
|
|
58
|
+
case "insertText":
|
|
59
|
+
apiRequest.insertText = req.params;
|
|
60
|
+
break;
|
|
61
|
+
case "deleteContentRange":
|
|
62
|
+
apiRequest.deleteContentRange = req.params;
|
|
63
|
+
break;
|
|
64
|
+
case "updateTextStyle":
|
|
65
|
+
apiRequest.updateTextStyle = req.params;
|
|
66
|
+
break;
|
|
67
|
+
case "updateParagraphStyle":
|
|
68
|
+
apiRequest.updateParagraphStyle = req.params;
|
|
69
|
+
break;
|
|
70
|
+
case "createParagraphBullets":
|
|
71
|
+
apiRequest.createParagraphBullets = req.params;
|
|
72
|
+
break;
|
|
73
|
+
case "insertTable":
|
|
74
|
+
apiRequest.insertTable = req.params;
|
|
75
|
+
break;
|
|
76
|
+
case "insertInlineImage":
|
|
77
|
+
apiRequest.insertInlineImage = req.params;
|
|
78
|
+
break;
|
|
79
|
+
case "insertPageBreak":
|
|
80
|
+
apiRequest.insertPageBreak = req.params;
|
|
81
|
+
break;
|
|
82
|
+
case "createHeader":
|
|
83
|
+
apiRequest.createHeader = req.params;
|
|
84
|
+
break;
|
|
85
|
+
case "createFooter":
|
|
86
|
+
apiRequest.createFooter = req.params;
|
|
87
|
+
break;
|
|
88
|
+
default:
|
|
89
|
+
throw new Error(`Unknown request type: ${req.type}`);
|
|
90
|
+
}
|
|
91
|
+
return apiRequest;
|
|
92
|
+
});
|
|
93
|
+
const response = await docs.documents.batchUpdate({
|
|
94
|
+
documentId,
|
|
95
|
+
requestBody: {
|
|
96
|
+
requests,
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
return ResponseFormatter.success({
|
|
100
|
+
documentId,
|
|
101
|
+
operationsExecuted: args.requests.length,
|
|
102
|
+
requestTypes: args.requests.map((r) => r.type),
|
|
103
|
+
}, `Successfully executed ${args.requests.length} operation(s)`);
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
return ResponseFormatter.error(error);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
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_create_table",
|
|
7
|
+
description: "Create a table with specified rows and columns 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
|
+
rows: {
|
|
16
|
+
type: "number",
|
|
17
|
+
description: "Number of rows in the table",
|
|
18
|
+
},
|
|
19
|
+
columns: {
|
|
20
|
+
type: "number",
|
|
21
|
+
description: "Number of columns in the table",
|
|
22
|
+
},
|
|
23
|
+
index: {
|
|
24
|
+
type: "number",
|
|
25
|
+
description: "Position to insert table (1-based)",
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
required: ["documentId", "rows", "columns", "index"],
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
export async function createTable(args) {
|
|
32
|
+
try {
|
|
33
|
+
// Validate inputs
|
|
34
|
+
if (!Validator.validateIndex(args.index)) {
|
|
35
|
+
throw new Error("Index must be a positive integer");
|
|
36
|
+
}
|
|
37
|
+
if (!Validator.validateIndex(args.rows) || args.rows < 1 || args.rows > 20) {
|
|
38
|
+
throw new Error("Rows must be between 1 and 20");
|
|
39
|
+
}
|
|
40
|
+
if (!Validator.validateIndex(args.columns) || args.columns < 1 || args.columns > 20) {
|
|
41
|
+
throw new Error("Columns must be between 1 and 20");
|
|
42
|
+
}
|
|
43
|
+
// Resolve document ID from URL if needed
|
|
44
|
+
const docRef = DocumentIdResolver.resolve(args.documentId);
|
|
45
|
+
const documentId = docRef.id;
|
|
46
|
+
const docs = google.docs("v1");
|
|
47
|
+
const response = await docs.documents.batchUpdate({
|
|
48
|
+
documentId,
|
|
49
|
+
requestBody: {
|
|
50
|
+
requests: [
|
|
51
|
+
{
|
|
52
|
+
insertTable: {
|
|
53
|
+
rows: args.rows,
|
|
54
|
+
columns: args.columns,
|
|
55
|
+
location: {
|
|
56
|
+
index: args.index,
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
return ResponseFormatter.success({
|
|
64
|
+
documentId,
|
|
65
|
+
rows: args.rows,
|
|
66
|
+
columns: args.columns,
|
|
67
|
+
insertedAt: args.index,
|
|
68
|
+
}, `Table created successfully (${args.rows}x${args.columns})`);
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
return ResponseFormatter.error(error);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
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_export",
|
|
6
|
+
description: "Export a Google Document to different formats (PDF, DOCX, Plain Text, HTML, EPUB).",
|
|
7
|
+
inputSchema: {
|
|
8
|
+
type: "object",
|
|
9
|
+
properties: {
|
|
10
|
+
documentId: {
|
|
11
|
+
type: "string",
|
|
12
|
+
description: "Document ID or full Google Docs URL",
|
|
13
|
+
},
|
|
14
|
+
mimeType: {
|
|
15
|
+
type: "string",
|
|
16
|
+
description: "Export format",
|
|
17
|
+
enum: [
|
|
18
|
+
"application/pdf",
|
|
19
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
20
|
+
"text/plain",
|
|
21
|
+
"text/html",
|
|
22
|
+
"application/epub+zip",
|
|
23
|
+
],
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
required: ["documentId", "mimeType"],
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
export async function exportDocument(args) {
|
|
30
|
+
try {
|
|
31
|
+
// Resolve document ID from URL if needed
|
|
32
|
+
const docRef = DocumentIdResolver.resolve(args.documentId);
|
|
33
|
+
const documentId = docRef.id;
|
|
34
|
+
const drive = google.drive("v3");
|
|
35
|
+
// Export the document
|
|
36
|
+
const response = await drive.files.export({
|
|
37
|
+
fileId: documentId,
|
|
38
|
+
mimeType: args.mimeType,
|
|
39
|
+
}, { responseType: "arraybuffer" });
|
|
40
|
+
// Get file size
|
|
41
|
+
const fileSize = Buffer.byteLength(response.data);
|
|
42
|
+
// Map MIME type to file extension
|
|
43
|
+
const extensionMap = {
|
|
44
|
+
"application/pdf": "pdf",
|
|
45
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document": "docx",
|
|
46
|
+
"text/plain": "txt",
|
|
47
|
+
"text/html": "html",
|
|
48
|
+
"application/epub+zip": "epub",
|
|
49
|
+
};
|
|
50
|
+
const extension = extensionMap[args.mimeType];
|
|
51
|
+
return ResponseFormatter.success({
|
|
52
|
+
documentId,
|
|
53
|
+
mimeType: args.mimeType,
|
|
54
|
+
extension,
|
|
55
|
+
fileSize,
|
|
56
|
+
note: "Document exported successfully. The exported file data is available in the API response but not shown here for brevity.",
|
|
57
|
+
}, "Document exported successfully");
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
return ResponseFormatter.error(error);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -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
|
+
}
|