@triffon/google-docs-mcp 1.11.3-triffon
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/LICENSE +7 -0
- package/README.md +567 -0
- package/dist/auth.js +327 -0
- package/dist/cachedToolsList.js +38 -0
- package/dist/clients.js +180 -0
- package/dist/config.js +9 -0
- package/dist/downloadProxy.js +82 -0
- package/dist/driveQueryUtils.js +7 -0
- package/dist/firestoreTokenStorage.js +32 -0
- package/dist/googleDocsApiHelpers.js +1257 -0
- package/dist/googleSheetsApiHelpers.js +849 -0
- package/dist/index.js +240 -0
- package/dist/landingPage.js +103 -0
- package/dist/logger.js +58 -0
- package/dist/markdown-transformer/docsToMarkdown.js +259 -0
- package/dist/markdown-transformer/index.js +128 -0
- package/dist/markdown-transformer/markdownToDocs.js +834 -0
- package/dist/remoteWrapper.js +72 -0
- package/dist/tools/calendar/createEvent.js +89 -0
- package/dist/tools/calendar/deleteEvent.js +49 -0
- package/dist/tools/calendar/helpers.js +19 -0
- package/dist/tools/calendar/index.js +12 -0
- package/dist/tools/calendar/listEvents.js +84 -0
- package/dist/tools/calendar/quickAddEvent.js +55 -0
- package/dist/tools/calendar/updateEvent.js +84 -0
- package/dist/tools/docs/addTab.js +84 -0
- package/dist/tools/docs/appendTableRows.js +89 -0
- package/dist/tools/docs/appendToGoogleDoc.js +85 -0
- package/dist/tools/docs/cloneTable.js +159 -0
- package/dist/tools/docs/comments/addComment.js +83 -0
- package/dist/tools/docs/comments/deleteComment.js +30 -0
- package/dist/tools/docs/comments/getComment.js +45 -0
- package/dist/tools/docs/comments/index.js +14 -0
- package/dist/tools/docs/comments/listComments.js +43 -0
- package/dist/tools/docs/comments/replyToComment.js +35 -0
- package/dist/tools/docs/comments/resolveComment.js +55 -0
- package/dist/tools/docs/deleteRange.js +61 -0
- package/dist/tools/docs/deleteTableRows.js +62 -0
- package/dist/tools/docs/findAndReplace.js +54 -0
- package/dist/tools/docs/findElement.js +43 -0
- package/dist/tools/docs/findSectionsByHeading.js +46 -0
- package/dist/tools/docs/formatting/applyParagraphStyle.js +83 -0
- package/dist/tools/docs/formatting/applyTextStyle.js +49 -0
- package/dist/tools/docs/formatting/batchApplyTextStyle.js +86 -0
- package/dist/tools/docs/formatting/index.js +16 -0
- package/dist/tools/docs/formatting/updateTableBorders.js +81 -0
- package/dist/tools/docs/formatting/updateTableCellStyle.js +85 -0
- package/dist/tools/docs/formatting/updateTableColumnWidth.js +51 -0
- package/dist/tools/docs/formatting/updateTableRowStyle.js +74 -0
- package/dist/tools/docs/getTableStructure.js +48 -0
- package/dist/tools/docs/index.js +66 -0
- package/dist/tools/docs/insertDateChip.js +82 -0
- package/dist/tools/docs/insertImage.js +112 -0
- package/dist/tools/docs/insertPageBreak.js +47 -0
- package/dist/tools/docs/insertPerson.js +53 -0
- package/dist/tools/docs/insertRichLink.js +58 -0
- package/dist/tools/docs/insertSectionBreak.js +60 -0
- package/dist/tools/docs/insertTable.js +42 -0
- package/dist/tools/docs/insertTableWithData.js +125 -0
- package/dist/tools/docs/insertText.js +49 -0
- package/dist/tools/docs/listDocumentTables.js +47 -0
- package/dist/tools/docs/listDocumentTabs.js +59 -0
- package/dist/tools/docs/listSmartChips.js +41 -0
- package/dist/tools/docs/modifyText.js +147 -0
- package/dist/tools/docs/readGoogleDoc.js +164 -0
- package/dist/tools/docs/renameTab.js +47 -0
- package/dist/tools/docs/replaceTableRowData.js +55 -0
- package/dist/tools/docs/smartChipHelpers.js +71 -0
- package/dist/tools/docs/structureHelpers.js +250 -0
- package/dist/tools/docs/tabFieldMasks.js +48 -0
- package/dist/tools/docs/tableRowDataHelpers.js +53 -0
- package/dist/tools/docs/updateSectionStyle.js +148 -0
- package/dist/tools/drive/copyFile.js +63 -0
- package/dist/tools/drive/createDocument.js +105 -0
- package/dist/tools/drive/createFolder.js +48 -0
- package/dist/tools/drive/createFromTemplate.js +82 -0
- package/dist/tools/drive/deleteFile.js +72 -0
- package/dist/tools/drive/downloadFile.js +266 -0
- package/dist/tools/drive/getDocumentInfo.js +48 -0
- package/dist/tools/drive/getFolderInfo.js +48 -0
- package/dist/tools/drive/index.js +34 -0
- package/dist/tools/drive/listDriveFiles.js +129 -0
- package/dist/tools/drive/listFolderContents.js +83 -0
- package/dist/tools/drive/listGoogleDocs.js +70 -0
- package/dist/tools/drive/moveFile.js +54 -0
- package/dist/tools/drive/renameFile.js +39 -0
- package/dist/tools/drive/savePathGuard.js +86 -0
- package/dist/tools/drive/searchDriveFiles.js +148 -0
- package/dist/tools/drive/searchGoogleDocs.js +77 -0
- package/dist/tools/drive/setFilePermission.js +68 -0
- package/dist/tools/gmail/createDraft.js +58 -0
- package/dist/tools/gmail/deleteDraft.js +37 -0
- package/dist/tools/gmail/getDraft.js +52 -0
- package/dist/tools/gmail/getMessage.js +92 -0
- package/dist/tools/gmail/helpers.js +113 -0
- package/dist/tools/gmail/index.js +28 -0
- package/dist/tools/gmail/listDrafts.js +74 -0
- package/dist/tools/gmail/listLabels.js +31 -0
- package/dist/tools/gmail/listMessages.js +87 -0
- package/dist/tools/gmail/modifyMessageLabels.js +54 -0
- package/dist/tools/gmail/sendDraft.js +42 -0
- package/dist/tools/gmail/sendEmail.js +56 -0
- package/dist/tools/gmail/trashMessage.js +37 -0
- package/dist/tools/gmail/triageInbox.js +147 -0
- package/dist/tools/gmail/updateDraft.js +60 -0
- package/dist/tools/index.js +64 -0
- package/dist/tools/script/appsScriptShared.js +96 -0
- package/dist/tools/script/createAppsScriptProject.js +74 -0
- package/dist/tools/script/getAppsScriptContent.js +56 -0
- package/dist/tools/script/index.js +14 -0
- package/dist/tools/script/updateAppsScriptContent.js +56 -0
- package/dist/tools/sheets/addConditionalFormatting.js +143 -0
- package/dist/tools/sheets/addSpreadsheetSheet.js +34 -0
- package/dist/tools/sheets/appendSpreadsheetRows.js +44 -0
- package/dist/tools/sheets/appendTableRows.js +51 -0
- package/dist/tools/sheets/autoResizeColumns.js +67 -0
- package/dist/tools/sheets/autoResizeRows.js +63 -0
- package/dist/tools/sheets/batchWrite.js +61 -0
- package/dist/tools/sheets/clearSpreadsheetRange.js +31 -0
- package/dist/tools/sheets/comments/commentAnchor.js +95 -0
- package/dist/tools/sheets/comments/createSheetsCellNote.js +33 -0
- package/dist/tools/sheets/comments/createSheetsComment.js +168 -0
- package/dist/tools/sheets/comments/deleteSheetsComment.js +32 -0
- package/dist/tools/sheets/comments/getSheetsComment.js +51 -0
- package/dist/tools/sheets/comments/index.js +16 -0
- package/dist/tools/sheets/comments/listSheetsComments.js +177 -0
- package/dist/tools/sheets/comments/replyToSheetsComment.js +37 -0
- package/dist/tools/sheets/comments/resolveSheetsComment.js +53 -0
- package/dist/tools/sheets/copyFormatting.js +59 -0
- package/dist/tools/sheets/copySheetTo.js +36 -0
- package/dist/tools/sheets/createSpreadsheet.js +72 -0
- package/dist/tools/sheets/createTable.js +120 -0
- package/dist/tools/sheets/deleteChart.js +41 -0
- package/dist/tools/sheets/deleteConditionalFormatting.js +46 -0
- package/dist/tools/sheets/deleteSheet.js +43 -0
- package/dist/tools/sheets/deleteTable.js +56 -0
- package/dist/tools/sheets/duplicateSheet.js +53 -0
- package/dist/tools/sheets/formatCells.js +122 -0
- package/dist/tools/sheets/freezeRowsAndColumns.js +58 -0
- package/dist/tools/sheets/getConditionalFormatting.js +98 -0
- package/dist/tools/sheets/getSpreadsheetInfo.js +44 -0
- package/dist/tools/sheets/getTable.js +48 -0
- package/dist/tools/sheets/groupRows.js +62 -0
- package/dist/tools/sheets/index.js +84 -0
- package/dist/tools/sheets/insertChart.js +225 -0
- package/dist/tools/sheets/listGoogleSheets.js +66 -0
- package/dist/tools/sheets/listTables.js +55 -0
- package/dist/tools/sheets/protectRange.js +59 -0
- package/dist/tools/sheets/readCellFormat.js +143 -0
- package/dist/tools/sheets/readSpreadsheet.js +36 -0
- package/dist/tools/sheets/renameSheet.js +48 -0
- package/dist/tools/sheets/setCellBorders.js +94 -0
- package/dist/tools/sheets/setColumnWidths.js +43 -0
- package/dist/tools/sheets/setDropdownValidation.js +51 -0
- package/dist/tools/sheets/setRowHeights.js +63 -0
- package/dist/tools/sheets/ungroupAllRows.js +66 -0
- package/dist/tools/sheets/updateTableRange.js +51 -0
- package/dist/tools/sheets/writeSpreadsheet.js +45 -0
- package/dist/tools/utils/appendMarkdownToGoogleDoc.js +95 -0
- package/dist/tools/utils/index.js +8 -0
- package/dist/tools/utils/replaceDocumentWithMarkdown.js +161 -0
- package/dist/tools/utils/replaceRangeWithMarkdown.js +72 -0
- package/dist/types.js +208 -0
- package/dist/upstreamAuth.js +62 -0
- package/package.json +51 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { UserError } from 'fastmcp';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getDocsClient } from '../../../clients.js';
|
|
4
|
+
import { DocumentIdParameter } from '../../../types.js';
|
|
5
|
+
import { getTableById } from '../structureHelpers.js';
|
|
6
|
+
import * as GDocsHelpers from '../../../googleDocsApiHelpers.js';
|
|
7
|
+
import { TABLE_INDEX_BODY_FIELDS, buildDocumentGetFields } from '../tabFieldMasks.js';
|
|
8
|
+
export function register(server) {
|
|
9
|
+
server.addTool({
|
|
10
|
+
name: 'updateTableColumnWidth',
|
|
11
|
+
description: 'Sets fixed widths for one or more columns in an existing Google Docs table.',
|
|
12
|
+
parameters: DocumentIdParameter.extend({
|
|
13
|
+
tableId: z.string().min(1).describe('The MCP table ID returned by listDocumentTables.'),
|
|
14
|
+
columnIndices: z
|
|
15
|
+
.array(z.number().int().min(0))
|
|
16
|
+
.min(1)
|
|
17
|
+
.describe('Zero-based column indices to update.'),
|
|
18
|
+
widthPt: z.number().min(1).describe('Fixed width in points.'),
|
|
19
|
+
tabId: z.string().optional().describe('Optional target tab ID.'),
|
|
20
|
+
}),
|
|
21
|
+
execute: async (args, { log }) => {
|
|
22
|
+
const docs = await getDocsClient();
|
|
23
|
+
log.info(`Updating table column widths in ${args.tableId} for doc ${args.documentId}${args.tabId ? ` (tab: ${args.tabId})` : ''}`);
|
|
24
|
+
try {
|
|
25
|
+
const res = await docs.documents.get({
|
|
26
|
+
documentId: args.documentId,
|
|
27
|
+
...(args.tabId && { includeTabsContent: true }),
|
|
28
|
+
fields: buildDocumentGetFields(TABLE_INDEX_BODY_FIELDS, args.tabId),
|
|
29
|
+
});
|
|
30
|
+
const table = getTableById(res.data, args.tableId, args.tabId);
|
|
31
|
+
if (!table)
|
|
32
|
+
throw new UserError(`Table "${args.tableId}" not found in document.`);
|
|
33
|
+
if (table.startIndex == null) {
|
|
34
|
+
throw new UserError(`Table "${args.tableId}" does not expose a valid table start index.`);
|
|
35
|
+
}
|
|
36
|
+
if (args.columnIndices.some((index) => index >= table.columnCount)) {
|
|
37
|
+
throw new UserError(`One or more column indices exceed table ${args.tableId} column count ${table.columnCount}.`);
|
|
38
|
+
}
|
|
39
|
+
const request = GDocsHelpers.buildTableColumnWidthRequest(table.startIndex, args.columnIndices, args.widthPt, args.tabId);
|
|
40
|
+
await GDocsHelpers.executeBatchUpdate(docs, args.documentId, [request]);
|
|
41
|
+
return `Successfully updated width for ${args.columnIndices.length} column(s) in ${args.tableId}.`;
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
log.error(`Error updating column widths for ${args.tableId} in doc ${args.documentId}: ${error.message || error}`);
|
|
45
|
+
if (error instanceof UserError)
|
|
46
|
+
throw error;
|
|
47
|
+
throw new UserError(`Failed to update table column width: ${error.message || 'Unknown error'}`);
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { UserError } from 'fastmcp';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getDocsClient } from '../../../clients.js';
|
|
4
|
+
import { DocumentIdParameter } from '../../../types.js';
|
|
5
|
+
import { getTableById } from '../structureHelpers.js';
|
|
6
|
+
import * as GDocsHelpers from '../../../googleDocsApiHelpers.js';
|
|
7
|
+
import { TABLE_INDEX_BODY_FIELDS, buildDocumentGetFields } from '../tabFieldMasks.js';
|
|
8
|
+
export function register(server) {
|
|
9
|
+
server.addTool({
|
|
10
|
+
name: 'updateTableRowStyle',
|
|
11
|
+
description: 'Applies row-level styling to a Google Docs table, including minimum row height and optional pinned header rows.',
|
|
12
|
+
parameters: DocumentIdParameter.extend({
|
|
13
|
+
tableId: z.string().min(1).describe('The MCP table ID returned by listDocumentTables.'),
|
|
14
|
+
rowIndices: z
|
|
15
|
+
.array(z.number().int().min(0))
|
|
16
|
+
.min(1)
|
|
17
|
+
.describe('Zero-based row indices to style.'),
|
|
18
|
+
minRowHeightPt: z.number().min(0).optional().describe('Minimum row height in points.'),
|
|
19
|
+
preventOverflow: z
|
|
20
|
+
.boolean()
|
|
21
|
+
.optional()
|
|
22
|
+
.describe('Whether row content should avoid overflowing outside the row.'),
|
|
23
|
+
pinnedHeaderRowsCount: z
|
|
24
|
+
.number()
|
|
25
|
+
.int()
|
|
26
|
+
.min(0)
|
|
27
|
+
.optional()
|
|
28
|
+
.describe('Optional number of header rows to pin at the top of the table.'),
|
|
29
|
+
tabId: z.string().optional().describe('Optional target tab ID.'),
|
|
30
|
+
}).refine((data) => data.minRowHeightPt !== undefined ||
|
|
31
|
+
data.preventOverflow !== undefined ||
|
|
32
|
+
data.pinnedHeaderRowsCount !== undefined, {
|
|
33
|
+
message: 'At least one row style option must be provided.',
|
|
34
|
+
}),
|
|
35
|
+
execute: async (args, { log }) => {
|
|
36
|
+
const docs = await getDocsClient();
|
|
37
|
+
log.info(`Updating table row style in ${args.tableId} for doc ${args.documentId}${args.tabId ? ` (tab: ${args.tabId})` : ''}`);
|
|
38
|
+
try {
|
|
39
|
+
const res = await docs.documents.get({
|
|
40
|
+
documentId: args.documentId,
|
|
41
|
+
...(args.tabId && { includeTabsContent: true }),
|
|
42
|
+
fields: buildDocumentGetFields(TABLE_INDEX_BODY_FIELDS, args.tabId),
|
|
43
|
+
});
|
|
44
|
+
const table = getTableById(res.data, args.tableId, args.tabId);
|
|
45
|
+
if (!table)
|
|
46
|
+
throw new UserError(`Table "${args.tableId}" not found in document.`);
|
|
47
|
+
if (table.startIndex == null) {
|
|
48
|
+
throw new UserError(`Table "${args.tableId}" does not expose a valid table start index.`);
|
|
49
|
+
}
|
|
50
|
+
if (args.rowIndices.some((index) => index >= table.rowCount)) {
|
|
51
|
+
throw new UserError(`One or more row indices exceed table ${args.tableId} row count ${table.rowCount}.`);
|
|
52
|
+
}
|
|
53
|
+
const requests = [];
|
|
54
|
+
const styleRequest = GDocsHelpers.buildTableRowStyleRequest(table.startIndex, args.rowIndices, args.minRowHeightPt, args.preventOverflow, args.tabId);
|
|
55
|
+
if (styleRequest)
|
|
56
|
+
requests.push(styleRequest);
|
|
57
|
+
if (args.pinnedHeaderRowsCount !== undefined) {
|
|
58
|
+
requests.push(GDocsHelpers.buildPinTableHeaderRowsRequest(table.startIndex, args.pinnedHeaderRowsCount, args.tabId));
|
|
59
|
+
}
|
|
60
|
+
if (requests.length === 0) {
|
|
61
|
+
throw new UserError('No row style requests were generated.');
|
|
62
|
+
}
|
|
63
|
+
await GDocsHelpers.executeBatchUpdate(docs, args.documentId, requests);
|
|
64
|
+
return `Successfully updated row style for ${args.tableId}.`;
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
log.error(`Error updating table row style for ${args.tableId} in doc ${args.documentId}: ${error.message || error}`);
|
|
68
|
+
if (error instanceof UserError)
|
|
69
|
+
throw error;
|
|
70
|
+
throw new UserError(`Failed to update table row style: ${error.message || 'Unknown error'}`);
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { UserError } from 'fastmcp';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getDocsClient } from '../../clients.js';
|
|
4
|
+
import { DocumentIdParameter } from '../../types.js';
|
|
5
|
+
import { getTableById } from './structureHelpers.js';
|
|
6
|
+
import { TABLE_CONTENT_INDEXED_BODY_FIELDS, buildDocumentGetFields } from './tabFieldMasks.js';
|
|
7
|
+
export function register(server) {
|
|
8
|
+
server.addTool({
|
|
9
|
+
name: 'getTableStructure',
|
|
10
|
+
description: 'Returns detailed structure for a table in a Google Document, including row/column counts and extracted cell text.',
|
|
11
|
+
parameters: DocumentIdParameter.extend({
|
|
12
|
+
tableId: z
|
|
13
|
+
.string()
|
|
14
|
+
.min(1)
|
|
15
|
+
.describe('The MCP table ID returned by listDocumentTables, for example "table:body:0".'),
|
|
16
|
+
tabId: z
|
|
17
|
+
.string()
|
|
18
|
+
.optional()
|
|
19
|
+
.describe('The ID of the specific tab to inspect. If not specified, inspects the first tab or legacy document body.'),
|
|
20
|
+
}),
|
|
21
|
+
execute: async (args, { log }) => {
|
|
22
|
+
const docs = await getDocsClient();
|
|
23
|
+
log.info(`Getting table structure for ${args.tableId} in ${args.documentId}${args.tabId ? ` (tab: ${args.tabId})` : ''}`);
|
|
24
|
+
try {
|
|
25
|
+
const res = await docs.documents.get({
|
|
26
|
+
documentId: args.documentId,
|
|
27
|
+
...(args.tabId && { includeTabsContent: true }),
|
|
28
|
+
fields: buildDocumentGetFields(TABLE_CONTENT_INDEXED_BODY_FIELDS, args.tabId),
|
|
29
|
+
});
|
|
30
|
+
const table = getTableById(res.data, args.tableId, args.tabId);
|
|
31
|
+
if (!table) {
|
|
32
|
+
throw new UserError(`Table "${args.tableId}" not found in document.`);
|
|
33
|
+
}
|
|
34
|
+
return JSON.stringify(table, null, 2);
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
log.error(`Error getting table structure for ${args.tableId} in doc ${args.documentId}: ${error.message || error}`);
|
|
38
|
+
if (error instanceof UserError)
|
|
39
|
+
throw error;
|
|
40
|
+
if (error.code === 404)
|
|
41
|
+
throw new UserError(`Document not found (ID: ${args.documentId}).`);
|
|
42
|
+
if (error.code === 403)
|
|
43
|
+
throw new UserError(`Permission denied for document (ID: ${args.documentId}).`);
|
|
44
|
+
throw new UserError(`Failed to get table structure: ${error.message || 'Unknown error'}`);
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// Core read/write
|
|
2
|
+
import { register as readGoogleDoc } from './readGoogleDoc.js';
|
|
3
|
+
import { register as listDocumentTabs } from './listDocumentTabs.js';
|
|
4
|
+
import { register as renameTab } from './renameTab.js';
|
|
5
|
+
import { register as addTab } from './addTab.js';
|
|
6
|
+
import { register as appendToGoogleDoc } from './appendToGoogleDoc.js';
|
|
7
|
+
import { register as insertText } from './insertText.js';
|
|
8
|
+
import { register as deleteRange } from './deleteRange.js';
|
|
9
|
+
import { register as modifyText } from './modifyText.js';
|
|
10
|
+
import { register as findAndReplace } from './findAndReplace.js';
|
|
11
|
+
import { register as findElement } from './findElement.js';
|
|
12
|
+
// Structure
|
|
13
|
+
import { register as insertTable } from './insertTable.js';
|
|
14
|
+
import { register as insertTableWithData } from './insertTableWithData.js';
|
|
15
|
+
import { register as insertPageBreak } from './insertPageBreak.js';
|
|
16
|
+
import { register as insertSectionBreak } from './insertSectionBreak.js';
|
|
17
|
+
import { register as updateSectionStyle } from './updateSectionStyle.js';
|
|
18
|
+
import { register as insertImage } from './insertImage.js';
|
|
19
|
+
import { register as insertDateChip } from './insertDateChip.js';
|
|
20
|
+
import { register as insertPerson } from './insertPerson.js';
|
|
21
|
+
import { register as insertRichLink } from './insertRichLink.js';
|
|
22
|
+
import { register as listSmartChips } from './listSmartChips.js';
|
|
23
|
+
import { register as cloneTable } from './cloneTable.js';
|
|
24
|
+
import { register as listDocumentTables } from './listDocumentTables.js';
|
|
25
|
+
import { register as getTableStructure } from './getTableStructure.js';
|
|
26
|
+
import { register as findSectionsByHeading } from './findSectionsByHeading.js';
|
|
27
|
+
import { register as replaceTableRowData } from './replaceTableRowData.js';
|
|
28
|
+
import { register as appendTableRows } from './appendTableRows.js';
|
|
29
|
+
import { register as deleteTableRows } from './deleteTableRows.js';
|
|
30
|
+
// Sub-domains
|
|
31
|
+
import { registerCommentTools } from './comments/index.js';
|
|
32
|
+
import { registerFormattingTools } from './formatting/index.js';
|
|
33
|
+
export function registerDocsTools(server) {
|
|
34
|
+
// Core read/write
|
|
35
|
+
readGoogleDoc(server);
|
|
36
|
+
listDocumentTabs(server);
|
|
37
|
+
renameTab(server);
|
|
38
|
+
addTab(server);
|
|
39
|
+
appendToGoogleDoc(server);
|
|
40
|
+
insertText(server);
|
|
41
|
+
deleteRange(server);
|
|
42
|
+
modifyText(server);
|
|
43
|
+
findAndReplace(server);
|
|
44
|
+
findElement(server);
|
|
45
|
+
// Structure
|
|
46
|
+
insertTable(server);
|
|
47
|
+
insertTableWithData(server);
|
|
48
|
+
insertPageBreak(server);
|
|
49
|
+
insertSectionBreak(server);
|
|
50
|
+
updateSectionStyle(server);
|
|
51
|
+
insertImage(server);
|
|
52
|
+
insertDateChip(server);
|
|
53
|
+
insertPerson(server);
|
|
54
|
+
insertRichLink(server);
|
|
55
|
+
listSmartChips(server);
|
|
56
|
+
cloneTable(server);
|
|
57
|
+
listDocumentTables(server);
|
|
58
|
+
getTableStructure(server);
|
|
59
|
+
findSectionsByHeading(server);
|
|
60
|
+
replaceTableRowData(server);
|
|
61
|
+
appendTableRows(server);
|
|
62
|
+
deleteTableRows(server);
|
|
63
|
+
// Sub-domains
|
|
64
|
+
registerFormattingTools(server);
|
|
65
|
+
registerCommentTools(server);
|
|
66
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { UserError } from 'fastmcp';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getDocsClient } from '../../clients.js';
|
|
4
|
+
import { DocumentIdParameter } from '../../types.js';
|
|
5
|
+
import * as GDocsHelpers from '../../googleDocsApiHelpers.js';
|
|
6
|
+
const DateFormatSchema = z.enum([
|
|
7
|
+
'DATE_FORMAT_UNSPECIFIED',
|
|
8
|
+
'DATE_FORMAT_MONTH_DAY_ABBREVIATED',
|
|
9
|
+
'DATE_FORMAT_MONTH_DAY_FULL',
|
|
10
|
+
'DATE_FORMAT_MONTH_DAY_YEAR_ABBREVIATED',
|
|
11
|
+
'DATE_FORMAT_ISO8601',
|
|
12
|
+
]);
|
|
13
|
+
const TimeFormatSchema = z.enum([
|
|
14
|
+
'TIME_FORMAT_UNSPECIFIED',
|
|
15
|
+
'TIME_FORMAT_DISABLED',
|
|
16
|
+
'TIME_FORMAT_HOUR_MINUTE',
|
|
17
|
+
'TIME_FORMAT_HOUR_MINUTE_TIMEZONE',
|
|
18
|
+
]);
|
|
19
|
+
function toGoogleTimestamp(input) {
|
|
20
|
+
const date = new Date(input);
|
|
21
|
+
if (Number.isNaN(date.getTime())) {
|
|
22
|
+
throw new UserError(`Invalid date/time value: "${input}"`);
|
|
23
|
+
}
|
|
24
|
+
return date.toISOString();
|
|
25
|
+
}
|
|
26
|
+
export function register(server) {
|
|
27
|
+
server.addTool({
|
|
28
|
+
name: 'insertDateChip',
|
|
29
|
+
description: 'Inserts a Google Docs date smart chip at a specific paragraph location. This creates a real date element, not plain text.',
|
|
30
|
+
parameters: DocumentIdParameter.extend({
|
|
31
|
+
index: z
|
|
32
|
+
.number()
|
|
33
|
+
.int()
|
|
34
|
+
.min(1)
|
|
35
|
+
.describe('1-based character index within an existing paragraph where the date chip should be inserted.'),
|
|
36
|
+
date: z
|
|
37
|
+
.string()
|
|
38
|
+
.min(1)
|
|
39
|
+
.describe('Date or date-time input parseable by JavaScript Date, e.g. "2026-05-07T15:30:00+09:00".'),
|
|
40
|
+
timeZoneId: z
|
|
41
|
+
.string()
|
|
42
|
+
.optional()
|
|
43
|
+
.describe('Optional CLDR/IANA time zone, e.g. "Asia/Tokyo".'),
|
|
44
|
+
locale: z.string().optional().describe('Optional locale, e.g. "ja" or "en".'),
|
|
45
|
+
dateFormat: DateFormatSchema.optional().describe('How the date portion should be displayed.'),
|
|
46
|
+
timeFormat: TimeFormatSchema.optional().describe('How the time portion should be displayed.'),
|
|
47
|
+
tabId: z.string().optional().describe('Optional target tab ID.'),
|
|
48
|
+
}),
|
|
49
|
+
execute: async (args, { log }) => {
|
|
50
|
+
const docs = await getDocsClient();
|
|
51
|
+
log.info(`Inserting date chip into ${args.documentId} at index ${args.index}${args.tabId ? ` (tab: ${args.tabId})` : ''}`);
|
|
52
|
+
try {
|
|
53
|
+
if (args.tabId) {
|
|
54
|
+
const targetTab = await GDocsHelpers.getDocumentTab(docs, args.documentId, args.tabId);
|
|
55
|
+
}
|
|
56
|
+
const location = { index: args.index };
|
|
57
|
+
if (args.tabId)
|
|
58
|
+
location.tabId = args.tabId;
|
|
59
|
+
const request = {
|
|
60
|
+
insertDate: {
|
|
61
|
+
location: location,
|
|
62
|
+
dateElementProperties: {
|
|
63
|
+
timestamp: toGoogleTimestamp(args.date),
|
|
64
|
+
timeZoneId: args.timeZoneId,
|
|
65
|
+
locale: args.locale,
|
|
66
|
+
dateFormat: args.dateFormat,
|
|
67
|
+
timeFormat: args.timeFormat,
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
await GDocsHelpers.executeBatchUpdate(docs, args.documentId, [request]);
|
|
72
|
+
return `Successfully inserted a date chip at index ${args.index}${args.tabId ? ` in tab ${args.tabId}` : ''}.`;
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
log.error(`Error inserting date chip into doc ${args.documentId}: ${error.message || error}`);
|
|
76
|
+
if (error instanceof UserError)
|
|
77
|
+
throw error;
|
|
78
|
+
throw new UserError(`Failed to insert date chip: ${error.message || 'Unknown error'}`);
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { UserError } from 'fastmcp';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getDocsClient, getDriveClient, getScriptClient } from '../../clients.js';
|
|
4
|
+
import { DocumentIdParameter } from '../../types.js';
|
|
5
|
+
import * as GDocsHelpers from '../../googleDocsApiHelpers.js';
|
|
6
|
+
export function register(server) {
|
|
7
|
+
server.addTool({
|
|
8
|
+
name: 'insertImage',
|
|
9
|
+
description: 'Inserts an inline image into a Google Document. Provide either a publicly accessible URL or a local file path. Local files are automatically uploaded to Google Drive before insertion.',
|
|
10
|
+
parameters: DocumentIdParameter.extend({
|
|
11
|
+
imageUrl: z
|
|
12
|
+
.string()
|
|
13
|
+
.url()
|
|
14
|
+
.optional()
|
|
15
|
+
.describe('Publicly accessible URL to the image (http:// or https://).'),
|
|
16
|
+
localImagePath: z
|
|
17
|
+
.string()
|
|
18
|
+
.optional()
|
|
19
|
+
.describe('Absolute path to a local image file (supports .jpg, .jpeg, .png, .gif, .bmp, .webp, .svg). The file will be uploaded to Google Drive.'),
|
|
20
|
+
index: z
|
|
21
|
+
.number()
|
|
22
|
+
.int()
|
|
23
|
+
.min(1)
|
|
24
|
+
.describe("1-based character index in the document body where the image should be inserted. Use readDocument with format='json' to inspect indices."),
|
|
25
|
+
width: z.number().min(1).optional().describe('Width of the image in points.'),
|
|
26
|
+
height: z.number().min(1).optional().describe('Height of the image in points.'),
|
|
27
|
+
tabId: z
|
|
28
|
+
.string()
|
|
29
|
+
.optional()
|
|
30
|
+
.describe('The ID of the specific tab to insert into. Use listDocumentTabs to get tab IDs. If not specified, inserts into the first tab.'),
|
|
31
|
+
})
|
|
32
|
+
.refine((data) => data.imageUrl || data.localImagePath, {
|
|
33
|
+
message: 'Either imageUrl or localImagePath must be provided.',
|
|
34
|
+
})
|
|
35
|
+
.refine((data) => !(data.imageUrl && data.localImagePath), {
|
|
36
|
+
message: 'Provide only one of imageUrl or localImagePath, not both.',
|
|
37
|
+
}),
|
|
38
|
+
execute: async (args, { log }) => {
|
|
39
|
+
const docs = await getDocsClient();
|
|
40
|
+
const appsScriptDeploymentId = process.env.APPS_SCRIPT_DEPLOYMENT_ID;
|
|
41
|
+
try {
|
|
42
|
+
if (args.tabId) {
|
|
43
|
+
const targetTab = await GDocsHelpers.getDocumentTab(docs, args.documentId, args.tabId);
|
|
44
|
+
}
|
|
45
|
+
// --- Apps Script path: local files when APPS_SCRIPT_DEPLOYMENT_ID is set ---
|
|
46
|
+
if (args.localImagePath && appsScriptDeploymentId) {
|
|
47
|
+
const drive = await getDriveClient();
|
|
48
|
+
const scriptClient = await getScriptClient();
|
|
49
|
+
log.info(`[AppsScript] Uploading ${args.localImagePath} to Drive (no public sharing)`);
|
|
50
|
+
let parentFolderId;
|
|
51
|
+
try {
|
|
52
|
+
const docInfo = await drive.files.get({
|
|
53
|
+
fileId: args.documentId,
|
|
54
|
+
fields: 'parents',
|
|
55
|
+
supportsAllDrives: true,
|
|
56
|
+
});
|
|
57
|
+
if (docInfo.data.parents && docInfo.data.parents.length > 0) {
|
|
58
|
+
parentFolderId = docInfo.data.parents[0];
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
catch (folderError) {
|
|
62
|
+
log.warn(`Could not determine document's parent folder, using Drive root: ${folderError}`);
|
|
63
|
+
}
|
|
64
|
+
const driveFileId = await GDocsHelpers.uploadImageToDrive(drive, args.localImagePath, parentFolderId, true // skipPublicSharing
|
|
65
|
+
);
|
|
66
|
+
log.info(`[AppsScript] Inserting image via marker at index ${args.index} (fileId: ${driveFileId})`);
|
|
67
|
+
await GDocsHelpers.insertImageViaAppsScript(docs, scriptClient, appsScriptDeploymentId, args.documentId, driveFileId, args.index, args.tabId);
|
|
68
|
+
return `Successfully inserted local image at index ${args.index} via Apps Script${args.tabId ? ` in tab ${args.tabId}` : ''}.`;
|
|
69
|
+
}
|
|
70
|
+
// --- Standard path: public URL insertion via Docs API ---
|
|
71
|
+
let resolvedUrl;
|
|
72
|
+
if (args.localImagePath) {
|
|
73
|
+
const drive = await getDriveClient();
|
|
74
|
+
log.info(`Uploading local image ${args.localImagePath} and inserting at index ${args.index} in doc ${args.documentId}${args.tabId ? ` (tab: ${args.tabId})` : ''}`);
|
|
75
|
+
let parentFolderId;
|
|
76
|
+
try {
|
|
77
|
+
const docInfo = await drive.files.get({
|
|
78
|
+
fileId: args.documentId,
|
|
79
|
+
fields: 'parents',
|
|
80
|
+
supportsAllDrives: true,
|
|
81
|
+
});
|
|
82
|
+
if (docInfo.data.parents && docInfo.data.parents.length > 0) {
|
|
83
|
+
parentFolderId = docInfo.data.parents[0];
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
catch (folderError) {
|
|
87
|
+
log.warn(`Could not determine document's parent folder, using Drive root: ${folderError}`);
|
|
88
|
+
}
|
|
89
|
+
resolvedUrl = await GDocsHelpers.uploadImageToDrive(drive, args.localImagePath, parentFolderId, false // explicit: needs public URL for Docs API insertion
|
|
90
|
+
);
|
|
91
|
+
log.info(`Image uploaded successfully, URL: ${resolvedUrl}`);
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
resolvedUrl = args.imageUrl;
|
|
95
|
+
log.info(`Inserting image from URL ${resolvedUrl} at index ${args.index} in doc ${args.documentId}${args.tabId ? ` (tab: ${args.tabId})` : ''}`);
|
|
96
|
+
}
|
|
97
|
+
await GDocsHelpers.insertInlineImage(docs, args.documentId, resolvedUrl, args.index, args.width, args.height, args.tabId);
|
|
98
|
+
let sizeInfo = '';
|
|
99
|
+
if (args.width && args.height) {
|
|
100
|
+
sizeInfo = ` with size ${args.width}x${args.height}pt`;
|
|
101
|
+
}
|
|
102
|
+
return `Successfully inserted image at index ${args.index}${sizeInfo}${args.tabId ? ` in tab ${args.tabId}` : ''}.`;
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
log.error(`Error inserting image in doc ${args.documentId}: ${error.message || error}`);
|
|
106
|
+
if (error instanceof UserError)
|
|
107
|
+
throw error;
|
|
108
|
+
throw new UserError(`Failed to insert image: ${error.message || 'Unknown error'}`);
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { UserError } from 'fastmcp';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getDocsClient } from '../../clients.js';
|
|
4
|
+
import { DocumentIdParameter } from '../../types.js';
|
|
5
|
+
import * as GDocsHelpers from '../../googleDocsApiHelpers.js';
|
|
6
|
+
export function register(server) {
|
|
7
|
+
server.addTool({
|
|
8
|
+
name: 'insertPageBreak',
|
|
9
|
+
description: 'Inserts a page break at a character index in the document.',
|
|
10
|
+
parameters: DocumentIdParameter.extend({
|
|
11
|
+
index: z
|
|
12
|
+
.number()
|
|
13
|
+
.int()
|
|
14
|
+
.min(1)
|
|
15
|
+
.describe("1-based character index within the document body. Use readDocument with format='json' to inspect indices."),
|
|
16
|
+
tabId: z
|
|
17
|
+
.string()
|
|
18
|
+
.optional()
|
|
19
|
+
.describe('The ID of the specific tab to insert into. Use listDocumentTabs to get tab IDs. If not specified, inserts into the first tab.'),
|
|
20
|
+
}),
|
|
21
|
+
execute: async (args, { log }) => {
|
|
22
|
+
const docs = await getDocsClient();
|
|
23
|
+
log.info(`Inserting page break in doc ${args.documentId} at index ${args.index}${args.tabId ? ` (tab: ${args.tabId})` : ''}`);
|
|
24
|
+
try {
|
|
25
|
+
// If tabId is specified, verify the tab exists
|
|
26
|
+
if (args.tabId) {
|
|
27
|
+
const targetTab = await GDocsHelpers.getDocumentTab(docs, args.documentId, args.tabId);
|
|
28
|
+
}
|
|
29
|
+
const location = { index: args.index };
|
|
30
|
+
if (args.tabId) {
|
|
31
|
+
location.tabId = args.tabId;
|
|
32
|
+
}
|
|
33
|
+
const request = {
|
|
34
|
+
insertPageBreak: { location },
|
|
35
|
+
};
|
|
36
|
+
await GDocsHelpers.executeBatchUpdate(docs, args.documentId, [request]);
|
|
37
|
+
return `Successfully inserted page break at index ${args.index}${args.tabId ? ` in tab ${args.tabId}` : ''}.`;
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
log.error(`Error inserting page break in doc ${args.documentId}: ${error.message || error}`);
|
|
41
|
+
if (error instanceof UserError)
|
|
42
|
+
throw error;
|
|
43
|
+
throw new UserError(`Failed to insert page break: ${error.message || 'Unknown error'}`);
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { UserError } from 'fastmcp';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getDocsClient } from '../../clients.js';
|
|
4
|
+
import { DocumentIdParameter } from '../../types.js';
|
|
5
|
+
import * as GDocsHelpers from '../../googleDocsApiHelpers.js';
|
|
6
|
+
export function register(server) {
|
|
7
|
+
server.addTool({
|
|
8
|
+
name: 'insertPerson',
|
|
9
|
+
description: 'Inserts a Google Docs person smart chip at a specific paragraph location using an email address.',
|
|
10
|
+
parameters: DocumentIdParameter.extend({
|
|
11
|
+
index: z
|
|
12
|
+
.number()
|
|
13
|
+
.int()
|
|
14
|
+
.min(1)
|
|
15
|
+
.describe('1-based character index within an existing paragraph where the person chip should be inserted.'),
|
|
16
|
+
email: z.string().email().describe('Email address linked to the person smart chip.'),
|
|
17
|
+
name: z
|
|
18
|
+
.string()
|
|
19
|
+
.optional()
|
|
20
|
+
.describe('Optional display name hint when Google Docs resolves the person mention.'),
|
|
21
|
+
tabId: z.string().optional().describe('Optional target tab ID.'),
|
|
22
|
+
}),
|
|
23
|
+
execute: async (args, { log }) => {
|
|
24
|
+
const docs = await getDocsClient();
|
|
25
|
+
log.info(`Inserting person chip into ${args.documentId} at index ${args.index}${args.tabId ? ` (tab: ${args.tabId})` : ''}`);
|
|
26
|
+
try {
|
|
27
|
+
if (args.tabId) {
|
|
28
|
+
const targetTab = await GDocsHelpers.getDocumentTab(docs, args.documentId, args.tabId);
|
|
29
|
+
}
|
|
30
|
+
const location = { index: args.index };
|
|
31
|
+
if (args.tabId)
|
|
32
|
+
location.tabId = args.tabId;
|
|
33
|
+
const request = {
|
|
34
|
+
insertPerson: {
|
|
35
|
+
location: location,
|
|
36
|
+
personProperties: {
|
|
37
|
+
email: args.email,
|
|
38
|
+
name: args.name,
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
await GDocsHelpers.executeBatchUpdate(docs, args.documentId, [request]);
|
|
43
|
+
return `Successfully inserted a person chip at index ${args.index}${args.tabId ? ` in tab ${args.tabId}` : ''}.`;
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
log.error(`Error inserting person chip into doc ${args.documentId}: ${error.message || error}`);
|
|
47
|
+
if (error instanceof UserError)
|
|
48
|
+
throw error;
|
|
49
|
+
throw new UserError(`Failed to insert person chip: ${error.message || 'Unknown error'}`);
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { UserError } from 'fastmcp';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getDocsClient } from '../../clients.js';
|
|
4
|
+
import { DocumentIdParameter } from '../../types.js';
|
|
5
|
+
import * as GDocsHelpers from '../../googleDocsApiHelpers.js';
|
|
6
|
+
export function register(server) {
|
|
7
|
+
server.addTool({
|
|
8
|
+
name: 'insertRichLink',
|
|
9
|
+
description: 'Inserts a Google Docs rich link smart chip at a specific paragraph location. Use for Google resource links such as Drive files or Calendar events.',
|
|
10
|
+
parameters: DocumentIdParameter.extend({
|
|
11
|
+
index: z
|
|
12
|
+
.number()
|
|
13
|
+
.int()
|
|
14
|
+
.min(1)
|
|
15
|
+
.describe('1-based character index within an existing paragraph where the rich link should be inserted.'),
|
|
16
|
+
uri: z.string().url().describe('URI of the Google resource for the rich link smart chip.'),
|
|
17
|
+
mimeType: z
|
|
18
|
+
.string()
|
|
19
|
+
.optional()
|
|
20
|
+
.describe('Optional MIME type of the linked resource, if known.'),
|
|
21
|
+
title: z
|
|
22
|
+
.string()
|
|
23
|
+
.optional()
|
|
24
|
+
.describe('Optional title hint. Google Docs may still resolve and display the canonical resource title.'),
|
|
25
|
+
tabId: z.string().optional().describe('Optional target tab ID.'),
|
|
26
|
+
}),
|
|
27
|
+
execute: async (args, { log }) => {
|
|
28
|
+
const docs = await getDocsClient();
|
|
29
|
+
log.info(`Inserting rich link into ${args.documentId} at index ${args.index}${args.tabId ? ` (tab: ${args.tabId})` : ''}`);
|
|
30
|
+
try {
|
|
31
|
+
if (args.tabId) {
|
|
32
|
+
const targetTab = await GDocsHelpers.getDocumentTab(docs, args.documentId, args.tabId);
|
|
33
|
+
}
|
|
34
|
+
const location = { index: args.index };
|
|
35
|
+
if (args.tabId)
|
|
36
|
+
location.tabId = args.tabId;
|
|
37
|
+
const request = {
|
|
38
|
+
insertRichLink: {
|
|
39
|
+
location: location,
|
|
40
|
+
richLinkProperties: {
|
|
41
|
+
uri: args.uri,
|
|
42
|
+
mimeType: args.mimeType,
|
|
43
|
+
title: args.title,
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
await GDocsHelpers.executeBatchUpdate(docs, args.documentId, [request]);
|
|
48
|
+
return `Successfully inserted a rich link at index ${args.index}${args.tabId ? ` in tab ${args.tabId}` : ''}.`;
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
log.error(`Error inserting rich link into doc ${args.documentId}: ${error.message || error}`);
|
|
52
|
+
if (error instanceof UserError)
|
|
53
|
+
throw error;
|
|
54
|
+
throw new UserError(`Failed to insert rich link: ${error.message || 'Unknown error'}`);
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { UserError } from 'fastmcp';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getDocsClient } from '../../clients.js';
|
|
4
|
+
import { DocumentIdParameter } from '../../types.js';
|
|
5
|
+
import * as GDocsHelpers from '../../googleDocsApiHelpers.js';
|
|
6
|
+
export function buildInsertSectionBreakRequest(params) {
|
|
7
|
+
const location = { index: params.index };
|
|
8
|
+
if (params.tabId) {
|
|
9
|
+
location.tabId = params.tabId;
|
|
10
|
+
}
|
|
11
|
+
return {
|
|
12
|
+
insertSectionBreak: {
|
|
13
|
+
location,
|
|
14
|
+
sectionType: params.sectionType,
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export function register(server) {
|
|
19
|
+
server.addTool({
|
|
20
|
+
name: 'insertSectionBreak',
|
|
21
|
+
description: 'Inserts a section break at a character index in the document. A section break starts a new section whose style (page orientation, margins, columns, page numbering) can then be customized with updateSectionStyle. Use sectionType="NEXT_PAGE" when you want the new section to start on a fresh page (required for mixing portrait and landscape pages in a single document) and "CONTINUOUS" when the new section should begin inline without a page break.',
|
|
22
|
+
parameters: DocumentIdParameter.extend({
|
|
23
|
+
index: z
|
|
24
|
+
.number()
|
|
25
|
+
.int()
|
|
26
|
+
.min(1)
|
|
27
|
+
.describe("1-based character index within the document body where the section break will be inserted. Use readDocument with format='json' to inspect indices."),
|
|
28
|
+
sectionType: z
|
|
29
|
+
.enum(['NEXT_PAGE', 'CONTINUOUS'])
|
|
30
|
+
.default('NEXT_PAGE')
|
|
31
|
+
.describe('The type of section break. NEXT_PAGE starts the new section on the next page (required to change page orientation). CONTINUOUS starts the new section inline without a page break. Defaults to NEXT_PAGE.'),
|
|
32
|
+
tabId: z
|
|
33
|
+
.string()
|
|
34
|
+
.optional()
|
|
35
|
+
.describe('The ID of the specific tab to insert into. Use listDocumentTabs to get tab IDs. If not specified, inserts into the first tab.'),
|
|
36
|
+
}),
|
|
37
|
+
execute: async (args, { log }) => {
|
|
38
|
+
const docs = await getDocsClient();
|
|
39
|
+
log.info(`Inserting ${args.sectionType} section break in doc ${args.documentId} at index ${args.index}${args.tabId ? ` (tab: ${args.tabId})` : ''}`);
|
|
40
|
+
try {
|
|
41
|
+
if (args.tabId) {
|
|
42
|
+
const targetTab = await GDocsHelpers.getDocumentTab(docs, args.documentId, args.tabId);
|
|
43
|
+
}
|
|
44
|
+
const request = buildInsertSectionBreakRequest({
|
|
45
|
+
index: args.index,
|
|
46
|
+
sectionType: args.sectionType,
|
|
47
|
+
tabId: args.tabId,
|
|
48
|
+
});
|
|
49
|
+
await GDocsHelpers.executeBatchUpdate(docs, args.documentId, [request]);
|
|
50
|
+
return `Successfully inserted ${args.sectionType} section break at index ${args.index}${args.tabId ? ` in tab ${args.tabId}` : ''}.`;
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
log.error(`Error inserting section break in doc ${args.documentId}: ${error.message || error}`);
|
|
54
|
+
if (error instanceof UserError)
|
|
55
|
+
throw error;
|
|
56
|
+
throw new UserError(`Failed to insert section break: ${error.message || 'Unknown error'}`);
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
}
|