@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,98 @@
|
|
|
1
|
+
import { UserError } from 'fastmcp';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getSheetsClient } from '../../clients.js';
|
|
4
|
+
import * as SheetsHelpers from '../../googleSheetsApiHelpers.js';
|
|
5
|
+
function colIndexToLetters(index) {
|
|
6
|
+
let s = '';
|
|
7
|
+
let i = index;
|
|
8
|
+
do {
|
|
9
|
+
s = String.fromCharCode(65 + (i % 26)) + s;
|
|
10
|
+
i = Math.floor(i / 26) - 1;
|
|
11
|
+
} while (i >= 0);
|
|
12
|
+
return s;
|
|
13
|
+
}
|
|
14
|
+
function rgbToHex(rgb) {
|
|
15
|
+
if (!rgb)
|
|
16
|
+
return '#000000';
|
|
17
|
+
const r = Math.round((rgb.red ?? 0) * 255);
|
|
18
|
+
const g = Math.round((rgb.green ?? 0) * 255);
|
|
19
|
+
const b = Math.round((rgb.blue ?? 0) * 255);
|
|
20
|
+
return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`.toUpperCase();
|
|
21
|
+
}
|
|
22
|
+
export function register(server) {
|
|
23
|
+
server.addTool({
|
|
24
|
+
name: 'getConditionalFormatting',
|
|
25
|
+
description: 'Lists all conditional formatting rules for a sheet as JSON. Each rule includes its index (needed for deleteConditionalFormatting), kind (BOOLEAN or GRADIENT), ranges, condition type/values, and applied formats (colors, bold, italic).',
|
|
26
|
+
parameters: z.strictObject({
|
|
27
|
+
spreadsheetId: z
|
|
28
|
+
.string()
|
|
29
|
+
.describe('The spreadsheet ID — the long string between /d/ and /edit in a Google Sheets URL.'),
|
|
30
|
+
sheetName: z
|
|
31
|
+
.string()
|
|
32
|
+
.optional()
|
|
33
|
+
.describe('Name of the sheet/tab. Defaults to the first sheet if not provided.'),
|
|
34
|
+
}),
|
|
35
|
+
execute: async (args, { log }) => {
|
|
36
|
+
const sheets = await getSheetsClient();
|
|
37
|
+
log.info(`Getting conditional formatting rules for spreadsheet ${args.spreadsheetId}`);
|
|
38
|
+
try {
|
|
39
|
+
const sheetId = await SheetsHelpers.resolveSheetId(sheets, args.spreadsheetId, args.sheetName);
|
|
40
|
+
const response = await sheets.spreadsheets.get({
|
|
41
|
+
spreadsheetId: args.spreadsheetId,
|
|
42
|
+
fields: 'sheets(properties(sheetId,title),conditionalFormats)',
|
|
43
|
+
});
|
|
44
|
+
const sheet = response.data.sheets?.find((s) => s.properties?.sheetId === sheetId);
|
|
45
|
+
const rules = sheet?.conditionalFormats ?? [];
|
|
46
|
+
const sheetTitle = sheet?.properties?.title ?? null;
|
|
47
|
+
const ruleSummaries = rules.map((rule, idx) => {
|
|
48
|
+
const condition = rule.booleanRule?.condition;
|
|
49
|
+
const gradient = rule.gradientRule;
|
|
50
|
+
const fmt = rule.booleanRule?.format ?? {};
|
|
51
|
+
const ranges = (rule.ranges ?? []).map((r) => {
|
|
52
|
+
const startCol = r.startColumnIndex != null ? colIndexToLetters(r.startColumnIndex) : '';
|
|
53
|
+
const endCol = r.endColumnIndex != null ? colIndexToLetters(r.endColumnIndex - 1) : '';
|
|
54
|
+
const startRow = r.startRowIndex != null ? r.startRowIndex + 1 : '';
|
|
55
|
+
const endRow = r.endRowIndex != null ? r.endRowIndex : '';
|
|
56
|
+
return `${startCol}${startRow}:${endCol}${endRow}`;
|
|
57
|
+
});
|
|
58
|
+
const kind = gradient ? 'GRADIENT' : 'BOOLEAN';
|
|
59
|
+
const conditionType = condition?.type ?? (gradient ? 'GRADIENT' : null);
|
|
60
|
+
const conditionValues = (condition?.values ?? [])
|
|
61
|
+
.map((v) => v.userEnteredValue)
|
|
62
|
+
.filter((v) => typeof v === 'string');
|
|
63
|
+
const bg = fmt.backgroundColor;
|
|
64
|
+
const backgroundColor = bg
|
|
65
|
+
? rgbToHex({ red: bg.red ?? 0, green: bg.green ?? 0, blue: bg.blue ?? 0 })
|
|
66
|
+
: null;
|
|
67
|
+
const fg = fmt.textFormat?.foregroundColor;
|
|
68
|
+
const textColor = fg
|
|
69
|
+
? rgbToHex({ red: fg.red ?? 0, green: fg.green ?? 0, blue: fg.blue ?? 0 })
|
|
70
|
+
: null;
|
|
71
|
+
return {
|
|
72
|
+
index: idx,
|
|
73
|
+
kind,
|
|
74
|
+
ranges,
|
|
75
|
+
conditionType,
|
|
76
|
+
conditionValues,
|
|
77
|
+
backgroundColor,
|
|
78
|
+
textColor,
|
|
79
|
+
bold: fmt.textFormat?.bold ?? false,
|
|
80
|
+
italic: fmt.textFormat?.italic ?? false,
|
|
81
|
+
};
|
|
82
|
+
});
|
|
83
|
+
return JSON.stringify({
|
|
84
|
+
spreadsheetId: args.spreadsheetId,
|
|
85
|
+
sheetName: sheetTitle,
|
|
86
|
+
count: ruleSummaries.length,
|
|
87
|
+
rules: ruleSummaries,
|
|
88
|
+
}, null, 2);
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
log.error(`Error getting conditional formatting: ${error.message || error}`);
|
|
92
|
+
if (error instanceof UserError)
|
|
93
|
+
throw error;
|
|
94
|
+
throw new UserError(`Failed to get conditional formatting: ${error.message || 'Unknown error'}`);
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { UserError } from 'fastmcp';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getSheetsClient } from '../../clients.js';
|
|
4
|
+
import * as SheetsHelpers from '../../googleSheetsApiHelpers.js';
|
|
5
|
+
export function register(server) {
|
|
6
|
+
server.addTool({
|
|
7
|
+
name: 'getSpreadsheetInfo',
|
|
8
|
+
description: 'Gets metadata about a spreadsheet including its title, URL, and a list of all sheets with their dimensions.',
|
|
9
|
+
parameters: z.strictObject({
|
|
10
|
+
spreadsheetId: z
|
|
11
|
+
.string()
|
|
12
|
+
.describe('The spreadsheet ID — the long string between /d/ and /edit in a Google Sheets URL.'),
|
|
13
|
+
}),
|
|
14
|
+
execute: async (args, { log }) => {
|
|
15
|
+
const sheets = await getSheetsClient();
|
|
16
|
+
log.info(`Getting info for spreadsheet: ${args.spreadsheetId}`);
|
|
17
|
+
try {
|
|
18
|
+
const metadata = await SheetsHelpers.getSpreadsheetMetadata(sheets, args.spreadsheetId);
|
|
19
|
+
const sheetList = metadata.sheets || [];
|
|
20
|
+
return JSON.stringify({
|
|
21
|
+
title: metadata.properties?.title || 'Untitled',
|
|
22
|
+
id: metadata.spreadsheetId,
|
|
23
|
+
url: `https://docs.google.com/spreadsheets/d/${metadata.spreadsheetId}`,
|
|
24
|
+
sheets: sheetList.map((sheet) => {
|
|
25
|
+
const props = sheet.properties;
|
|
26
|
+
return {
|
|
27
|
+
title: props?.title || 'Untitled',
|
|
28
|
+
sheetId: props?.sheetId,
|
|
29
|
+
rows: props?.gridProperties?.rowCount || 0,
|
|
30
|
+
columns: props?.gridProperties?.columnCount || 0,
|
|
31
|
+
hidden: props?.hidden || false,
|
|
32
|
+
};
|
|
33
|
+
}),
|
|
34
|
+
}, null, 2);
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
log.error(`Error getting spreadsheet info ${args.spreadsheetId}: ${error.message || error}`);
|
|
38
|
+
if (error instanceof UserError)
|
|
39
|
+
throw error;
|
|
40
|
+
throw new UserError(`Failed to get spreadsheet info: ${error.message || 'Unknown error'}`);
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { UserError } from 'fastmcp';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getSheetsClient } from '../../clients.js';
|
|
4
|
+
import * as SheetsHelpers from '../../googleSheetsApiHelpers.js';
|
|
5
|
+
export function register(server) {
|
|
6
|
+
server.addTool({
|
|
7
|
+
name: 'getTable',
|
|
8
|
+
description: 'Gets detailed information about a specific table including its columns, range, and properties. Use the table name or ID returned by listTables.',
|
|
9
|
+
parameters: z.strictObject({
|
|
10
|
+
spreadsheetId: z
|
|
11
|
+
.string()
|
|
12
|
+
.describe('The spreadsheet ID — the long string between /d/ and /edit in a Google Sheets URL.'),
|
|
13
|
+
tableIdentifier: z
|
|
14
|
+
.string()
|
|
15
|
+
.describe('The table name or table ID. Names are resolved first, then IDs. Use listTables to see available tables.'),
|
|
16
|
+
}),
|
|
17
|
+
execute: async (args, { log }) => {
|
|
18
|
+
const sheets = await getSheetsClient();
|
|
19
|
+
log.info(`Getting table details for: ${args.tableIdentifier}`);
|
|
20
|
+
try {
|
|
21
|
+
const { table, sheetName, sheetId } = await SheetsHelpers.resolveTableIdentifier(sheets, args.spreadsheetId, args.tableIdentifier);
|
|
22
|
+
// Build detailed table information
|
|
23
|
+
const columns = table.columnProperties?.map((col) => ({
|
|
24
|
+
index: col.columnIndex,
|
|
25
|
+
name: col.columnName,
|
|
26
|
+
})) || [];
|
|
27
|
+
const range = table.range
|
|
28
|
+
? `${sheetName}!${SheetsHelpers.rowColToA1(table.range.startRowIndex || 0, table.range.startColumnIndex || 0)}:${SheetsHelpers.rowColToA1((table.range.endRowIndex || 1) - 1, (table.range.endColumnIndex || 1) - 1)}`
|
|
29
|
+
: 'Unknown';
|
|
30
|
+
return JSON.stringify({
|
|
31
|
+
tableId: table.tableId,
|
|
32
|
+
name: table.name,
|
|
33
|
+
sheetName,
|
|
34
|
+
sheetId,
|
|
35
|
+
range,
|
|
36
|
+
columns,
|
|
37
|
+
columnCount: table.columnProperties?.length || 0,
|
|
38
|
+
}, null, 2);
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
log.error(`Error getting table details: ${error.message || error}`);
|
|
42
|
+
if (error instanceof UserError)
|
|
43
|
+
throw error;
|
|
44
|
+
throw new UserError(`Failed to get table details: ${error.message || 'Unknown error'}`);
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { UserError } from 'fastmcp';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getSheetsClient } from '../../clients.js';
|
|
4
|
+
import * as SheetsHelpers from '../../googleSheetsApiHelpers.js';
|
|
5
|
+
export function register(server) {
|
|
6
|
+
server.addTool({
|
|
7
|
+
name: 'groupRows',
|
|
8
|
+
description: 'Creates collapsible row groups in a Google Sheet using the Sheets API addDimensionGroup request. Each group specifies a range of rows (1-based, inclusive) to collapse.',
|
|
9
|
+
parameters: z.strictObject({
|
|
10
|
+
spreadsheetId: z
|
|
11
|
+
.string()
|
|
12
|
+
.describe('The spreadsheet ID — the long string between /d/ and /edit in a Google Sheets URL.'),
|
|
13
|
+
sheetName: z
|
|
14
|
+
.string()
|
|
15
|
+
.optional()
|
|
16
|
+
.describe('Name of the sheet/tab. Defaults to the first sheet if not provided.'),
|
|
17
|
+
groups: z
|
|
18
|
+
.array(z.strictObject({
|
|
19
|
+
startRowIndex: z
|
|
20
|
+
.number()
|
|
21
|
+
.int()
|
|
22
|
+
.describe('1-based row number of the first row in the group (inclusive).'),
|
|
23
|
+
endRowIndex: z
|
|
24
|
+
.number()
|
|
25
|
+
.int()
|
|
26
|
+
.describe('1-based row number of the last row in the group (inclusive).'),
|
|
27
|
+
}))
|
|
28
|
+
.min(1)
|
|
29
|
+
.describe('Array of row ranges to group. Each entry is {startRowIndex, endRowIndex} using 1-based row numbers.'),
|
|
30
|
+
}),
|
|
31
|
+
execute: async (args, { log }) => {
|
|
32
|
+
const sheets = await getSheetsClient();
|
|
33
|
+
log.info(`Grouping rows in spreadsheet ${args.spreadsheetId}`);
|
|
34
|
+
try {
|
|
35
|
+
const sheetId = await SheetsHelpers.resolveSheetId(sheets, args.spreadsheetId, args.sheetName);
|
|
36
|
+
// Convert 1-based inclusive row numbers to 0-based exclusive indices
|
|
37
|
+
// required by the Sheets API DimensionRange
|
|
38
|
+
const requests = args.groups.map(({ startRowIndex, endRowIndex }) => ({
|
|
39
|
+
addDimensionGroup: {
|
|
40
|
+
range: {
|
|
41
|
+
sheetId,
|
|
42
|
+
dimension: 'ROWS',
|
|
43
|
+
startIndex: startRowIndex - 1, // 0-based, inclusive
|
|
44
|
+
endIndex: endRowIndex, // 0-based, exclusive (= 1-based inclusive)
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
}));
|
|
48
|
+
await sheets.spreadsheets.batchUpdate({
|
|
49
|
+
spreadsheetId: args.spreadsheetId,
|
|
50
|
+
requestBody: { requests },
|
|
51
|
+
});
|
|
52
|
+
return `Successfully created ${args.groups.length} row group(s).`;
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
log.error(`Error grouping rows: ${error.message || error}`);
|
|
56
|
+
if (error instanceof UserError)
|
|
57
|
+
throw error;
|
|
58
|
+
throw new UserError(`Failed to group rows: ${error.message || 'Unknown error'}`);
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { register as readSpreadsheet } from './readSpreadsheet.js';
|
|
2
|
+
import { register as writeSpreadsheet } from './writeSpreadsheet.js';
|
|
3
|
+
import { register as batchWrite } from './batchWrite.js';
|
|
4
|
+
import { register as appendSpreadsheetRows } from './appendSpreadsheetRows.js';
|
|
5
|
+
import { register as clearSpreadsheetRange } from './clearSpreadsheetRange.js';
|
|
6
|
+
import { register as getSpreadsheetInfo } from './getSpreadsheetInfo.js';
|
|
7
|
+
import { register as addSpreadsheetSheet } from './addSpreadsheetSheet.js';
|
|
8
|
+
import { register as createSpreadsheet } from './createSpreadsheet.js';
|
|
9
|
+
import { register as listGoogleSheets } from './listGoogleSheets.js';
|
|
10
|
+
import { register as deleteSheet } from './deleteSheet.js';
|
|
11
|
+
import { register as renameSheet } from './renameSheet.js';
|
|
12
|
+
import { register as duplicateSheet } from './duplicateSheet.js';
|
|
13
|
+
import { register as copySheetTo } from './copySheetTo.js';
|
|
14
|
+
// Formatting & validation
|
|
15
|
+
import { register as formatCells } from './formatCells.js';
|
|
16
|
+
import { register as readCellFormat } from './readCellFormat.js';
|
|
17
|
+
import { register as copyFormatting } from './copyFormatting.js';
|
|
18
|
+
import { register as freezeRowsAndColumns } from './freezeRowsAndColumns.js';
|
|
19
|
+
import { register as setColumnWidths } from './setColumnWidths.js';
|
|
20
|
+
import { register as autoResizeColumns } from './autoResizeColumns.js';
|
|
21
|
+
import { register as autoResizeRows } from './autoResizeRows.js';
|
|
22
|
+
import { register as setRowHeights } from './setRowHeights.js';
|
|
23
|
+
import { register as setCellBorders } from './setCellBorders.js';
|
|
24
|
+
import { register as protectRange } from './protectRange.js';
|
|
25
|
+
import { register as getConditionalFormatting } from './getConditionalFormatting.js';
|
|
26
|
+
import { register as deleteConditionalFormatting } from './deleteConditionalFormatting.js';
|
|
27
|
+
import { register as setDropdownValidation } from './setDropdownValidation.js';
|
|
28
|
+
import { register as addConditionalFormatting } from './addConditionalFormatting.js';
|
|
29
|
+
import { register as groupRows } from './groupRows.js';
|
|
30
|
+
import { register as ungroupAllRows } from './ungroupAllRows.js';
|
|
31
|
+
// Comments
|
|
32
|
+
import { registerSheetsCommentTools } from './comments/index.js';
|
|
33
|
+
// Tables
|
|
34
|
+
import { register as createTable } from './createTable.js';
|
|
35
|
+
import { register as listTables } from './listTables.js';
|
|
36
|
+
import { register as getTable } from './getTable.js';
|
|
37
|
+
import { register as deleteTable } from './deleteTable.js';
|
|
38
|
+
import { register as updateTableRange } from './updateTableRange.js';
|
|
39
|
+
import { register as appendTableRows } from './appendTableRows.js';
|
|
40
|
+
import { register as insertChart } from './insertChart.js';
|
|
41
|
+
import { register as deleteChart } from './deleteChart.js';
|
|
42
|
+
export function registerSheetsTools(server) {
|
|
43
|
+
readSpreadsheet(server);
|
|
44
|
+
writeSpreadsheet(server);
|
|
45
|
+
batchWrite(server);
|
|
46
|
+
appendSpreadsheetRows(server);
|
|
47
|
+
clearSpreadsheetRange(server);
|
|
48
|
+
getSpreadsheetInfo(server);
|
|
49
|
+
addSpreadsheetSheet(server);
|
|
50
|
+
createSpreadsheet(server);
|
|
51
|
+
listGoogleSheets(server);
|
|
52
|
+
deleteSheet(server);
|
|
53
|
+
renameSheet(server);
|
|
54
|
+
duplicateSheet(server);
|
|
55
|
+
copySheetTo(server);
|
|
56
|
+
// Formatting & validation
|
|
57
|
+
formatCells(server);
|
|
58
|
+
readCellFormat(server);
|
|
59
|
+
copyFormatting(server);
|
|
60
|
+
freezeRowsAndColumns(server);
|
|
61
|
+
setColumnWidths(server);
|
|
62
|
+
autoResizeColumns(server);
|
|
63
|
+
autoResizeRows(server);
|
|
64
|
+
setRowHeights(server);
|
|
65
|
+
setCellBorders(server);
|
|
66
|
+
protectRange(server);
|
|
67
|
+
getConditionalFormatting(server);
|
|
68
|
+
deleteConditionalFormatting(server);
|
|
69
|
+
setDropdownValidation(server);
|
|
70
|
+
addConditionalFormatting(server);
|
|
71
|
+
groupRows(server);
|
|
72
|
+
ungroupAllRows(server);
|
|
73
|
+
// Comments
|
|
74
|
+
registerSheetsCommentTools(server);
|
|
75
|
+
// Tables
|
|
76
|
+
createTable(server);
|
|
77
|
+
listTables(server);
|
|
78
|
+
getTable(server);
|
|
79
|
+
deleteTable(server);
|
|
80
|
+
updateTableRange(server);
|
|
81
|
+
appendTableRows(server);
|
|
82
|
+
insertChart(server);
|
|
83
|
+
deleteChart(server);
|
|
84
|
+
}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { UserError } from 'fastmcp';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getSheetsClient } from '../../clients.js';
|
|
4
|
+
import * as SheetsHelpers from '../../googleSheetsApiHelpers.js';
|
|
5
|
+
export function register(server) {
|
|
6
|
+
server.addTool({
|
|
7
|
+
name: 'insertChart',
|
|
8
|
+
description: 'Inserts a chart into a Google Sheet. Supports bar, column, line, area, scatter, pie, donut, and treemap (hierarchical) chart types. ' +
|
|
9
|
+
'For treemap charts, the data must have a label column and a parent label column (use empty string for root nodes) plus a numeric size column. ' +
|
|
10
|
+
'Chart is placed as an overlay at the specified anchor cell.',
|
|
11
|
+
parameters: z.strictObject({
|
|
12
|
+
spreadsheetId: z
|
|
13
|
+
.string()
|
|
14
|
+
.describe('The spreadsheet ID — the long string between /d/ and /edit in a Google Sheets URL.'),
|
|
15
|
+
sheetName: z
|
|
16
|
+
.string()
|
|
17
|
+
.optional()
|
|
18
|
+
.describe('Name of the sheet/tab containing the data. Defaults to the first sheet.'),
|
|
19
|
+
chartType: z
|
|
20
|
+
.enum(['BAR', 'COLUMN', 'LINE', 'AREA', 'SCATTER', 'PIE', 'DONUT', 'TREEMAP'])
|
|
21
|
+
.describe('Chart type to create.'),
|
|
22
|
+
stackedType: z
|
|
23
|
+
.enum(['NOT_STACKED', 'STACKED', 'PERCENT_STACKED'])
|
|
24
|
+
.default('NOT_STACKED')
|
|
25
|
+
.describe('For bar/column/area charts: whether to stack series. NOT_STACKED = grouped, STACKED = absolute stacked, PERCENT_STACKED = 100% stacked.'),
|
|
26
|
+
title: z.string().optional().describe('Chart title.'),
|
|
27
|
+
dataRange: z
|
|
28
|
+
.string()
|
|
29
|
+
.describe('A1 notation range of the data (e.g., "A1:E50"). Include the header row if present.'),
|
|
30
|
+
headerRow: z
|
|
31
|
+
.boolean()
|
|
32
|
+
.default(true)
|
|
33
|
+
.describe('Whether the first row of the data range is a header row.'),
|
|
34
|
+
// Column indices for flexible data mapping
|
|
35
|
+
labelColumnIndex: z
|
|
36
|
+
.number()
|
|
37
|
+
.int()
|
|
38
|
+
.min(1)
|
|
39
|
+
.optional()
|
|
40
|
+
.describe('For pie/donut/treemap: 1-based column index for node labels (default: 1). For treemap this is the leaf/child label.'),
|
|
41
|
+
parentColumnIndex: z
|
|
42
|
+
.number()
|
|
43
|
+
.int()
|
|
44
|
+
.min(1)
|
|
45
|
+
.optional()
|
|
46
|
+
.describe('For treemap: 1-based column index for parent node labels. Root nodes should have an empty string in this column.'),
|
|
47
|
+
valueColumnIndex: z
|
|
48
|
+
.number()
|
|
49
|
+
.int()
|
|
50
|
+
.min(1)
|
|
51
|
+
.optional()
|
|
52
|
+
.describe('For pie/donut/treemap: 1-based column index for the numeric size/value (default: 2).'),
|
|
53
|
+
// Chart position and size
|
|
54
|
+
anchorRow: z
|
|
55
|
+
.number()
|
|
56
|
+
.int()
|
|
57
|
+
.min(0)
|
|
58
|
+
.default(0)
|
|
59
|
+
.describe('Row index (0-based) of the anchor cell for chart placement.'),
|
|
60
|
+
anchorColumn: z
|
|
61
|
+
.number()
|
|
62
|
+
.int()
|
|
63
|
+
.min(0)
|
|
64
|
+
.default(6)
|
|
65
|
+
.describe('Column index (0-based) of the anchor cell for chart placement.'),
|
|
66
|
+
offsetXPixels: z
|
|
67
|
+
.number()
|
|
68
|
+
.int()
|
|
69
|
+
.default(0)
|
|
70
|
+
.describe('Horizontal offset in pixels from the anchor cell.'),
|
|
71
|
+
offsetYPixels: z
|
|
72
|
+
.number()
|
|
73
|
+
.int()
|
|
74
|
+
.default(0)
|
|
75
|
+
.describe('Vertical offset in pixels from the anchor cell.'),
|
|
76
|
+
widthPixels: z.number().int().default(600).describe('Chart width in pixels.'),
|
|
77
|
+
heightPixels: z.number().int().default(400).describe('Chart height in pixels.'),
|
|
78
|
+
}),
|
|
79
|
+
execute: async (args, { log }) => {
|
|
80
|
+
const sheets = await getSheetsClient();
|
|
81
|
+
log.info(`Inserting ${args.chartType} chart into spreadsheet ${args.spreadsheetId}`);
|
|
82
|
+
try {
|
|
83
|
+
const sheetId = await SheetsHelpers.resolveSheetId(sheets, args.spreadsheetId, args.sheetName);
|
|
84
|
+
const { a1Range } = SheetsHelpers.parseRange(args.dataRange);
|
|
85
|
+
const gridRange = SheetsHelpers.parseA1ToGridRange(a1Range, sheetId);
|
|
86
|
+
const startRow = gridRange.startRowIndex ?? 0;
|
|
87
|
+
const endRow = gridRange.endRowIndex ?? startRow + 1;
|
|
88
|
+
const startCol = gridRange.startColumnIndex ?? 0;
|
|
89
|
+
const endCol = gridRange.endColumnIndex ?? startCol + 1;
|
|
90
|
+
const dataStartRow = args.headerRow ? startRow + 1 : startRow;
|
|
91
|
+
const labelCol = startCol + (args.labelColumnIndex ? args.labelColumnIndex - 1 : 0);
|
|
92
|
+
const valueCol = startCol + (args.valueColumnIndex ? args.valueColumnIndex - 1 : 1);
|
|
93
|
+
const parentCol = startCol + (args.parentColumnIndex ? args.parentColumnIndex - 1 : 0);
|
|
94
|
+
const makeSourceRange = (colStart, colEnd, rowStart = dataStartRow) => ({
|
|
95
|
+
sources: [
|
|
96
|
+
{
|
|
97
|
+
sheetId,
|
|
98
|
+
startRowIndex: rowStart,
|
|
99
|
+
endRowIndex: endRow,
|
|
100
|
+
startColumnIndex: colStart,
|
|
101
|
+
endColumnIndex: colEnd,
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
});
|
|
105
|
+
let chartSpec = {};
|
|
106
|
+
if (args.chartType === 'PIE' || args.chartType === 'DONUT') {
|
|
107
|
+
chartSpec.pieChart = {
|
|
108
|
+
legendPosition: 'LABELED_LEGEND',
|
|
109
|
+
pieHole: args.chartType === 'DONUT' ? 0.5 : 0,
|
|
110
|
+
domain: {
|
|
111
|
+
data: { sourceRange: makeSourceRange(labelCol, labelCol + 1) },
|
|
112
|
+
},
|
|
113
|
+
series: {
|
|
114
|
+
data: { sourceRange: makeSourceRange(valueCol, valueCol + 1) },
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
else if (args.chartType === 'TREEMAP') {
|
|
119
|
+
chartSpec.treemapChart = {
|
|
120
|
+
labels: {
|
|
121
|
+
sourceRange: makeSourceRange(labelCol, labelCol + 1),
|
|
122
|
+
},
|
|
123
|
+
parentLabels: {
|
|
124
|
+
sourceRange: makeSourceRange(parentCol, parentCol + 1),
|
|
125
|
+
},
|
|
126
|
+
sizeData: {
|
|
127
|
+
sourceRange: makeSourceRange(valueCol, valueCol + 1),
|
|
128
|
+
},
|
|
129
|
+
colorData: {
|
|
130
|
+
sourceRange: makeSourceRange(valueCol, valueCol + 1),
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
// Basic chart types: BAR, COLUMN, LINE, AREA, SCATTER
|
|
136
|
+
// Each series must be a separate entry with a single-column source range.
|
|
137
|
+
const seriesCount = endCol - startCol - 1;
|
|
138
|
+
const series = Array.from({ length: seriesCount }, (_, i) => ({
|
|
139
|
+
series: {
|
|
140
|
+
sourceRange: {
|
|
141
|
+
sources: [
|
|
142
|
+
{
|
|
143
|
+
sheetId,
|
|
144
|
+
startRowIndex: startRow, // include header so Sheets names the series automatically
|
|
145
|
+
endRowIndex: endRow,
|
|
146
|
+
startColumnIndex: startCol + 1 + i,
|
|
147
|
+
endColumnIndex: startCol + 2 + i,
|
|
148
|
+
},
|
|
149
|
+
],
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
targetAxis: 'LEFT_AXIS',
|
|
153
|
+
}));
|
|
154
|
+
chartSpec.basicChart = {
|
|
155
|
+
chartType: args.chartType,
|
|
156
|
+
stackedType: args.stackedType,
|
|
157
|
+
legendPosition: 'BOTTOM_LEGEND',
|
|
158
|
+
axis: [
|
|
159
|
+
{ position: 'BOTTOM_AXIS', title: '' },
|
|
160
|
+
{ position: 'LEFT_AXIS', title: '' },
|
|
161
|
+
],
|
|
162
|
+
domains: [
|
|
163
|
+
{
|
|
164
|
+
domain: {
|
|
165
|
+
sourceRange: {
|
|
166
|
+
sources: [
|
|
167
|
+
{
|
|
168
|
+
sheetId,
|
|
169
|
+
startRowIndex: startRow,
|
|
170
|
+
endRowIndex: endRow,
|
|
171
|
+
startColumnIndex: startCol,
|
|
172
|
+
endColumnIndex: startCol + 1,
|
|
173
|
+
},
|
|
174
|
+
],
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
reversed: false,
|
|
178
|
+
},
|
|
179
|
+
],
|
|
180
|
+
series,
|
|
181
|
+
headerCount: args.headerRow ? 1 : 0,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
if (args.title) {
|
|
185
|
+
chartSpec.title = args.title;
|
|
186
|
+
}
|
|
187
|
+
const response = await sheets.spreadsheets.batchUpdate({
|
|
188
|
+
spreadsheetId: args.spreadsheetId,
|
|
189
|
+
requestBody: {
|
|
190
|
+
requests: [
|
|
191
|
+
{
|
|
192
|
+
addChart: {
|
|
193
|
+
chart: {
|
|
194
|
+
spec: chartSpec,
|
|
195
|
+
position: {
|
|
196
|
+
overlayPosition: {
|
|
197
|
+
anchorCell: {
|
|
198
|
+
sheetId,
|
|
199
|
+
rowIndex: args.anchorRow,
|
|
200
|
+
columnIndex: args.anchorColumn,
|
|
201
|
+
},
|
|
202
|
+
offsetXPixels: args.offsetXPixels,
|
|
203
|
+
offsetYPixels: args.offsetYPixels,
|
|
204
|
+
widthPixels: args.widthPixels,
|
|
205
|
+
heightPixels: args.heightPixels,
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
],
|
|
212
|
+
},
|
|
213
|
+
});
|
|
214
|
+
const chartId = response.data.replies?.[0]?.addChart?.chart?.chartId;
|
|
215
|
+
return `Chart created successfully${chartId ? ` (Chart ID: ${chartId})` : ''}.`;
|
|
216
|
+
}
|
|
217
|
+
catch (error) {
|
|
218
|
+
log.error(`Error inserting chart: ${error.message || error}`);
|
|
219
|
+
if (error instanceof UserError)
|
|
220
|
+
throw error;
|
|
221
|
+
throw new UserError(`Failed to insert chart: ${error.message || 'Unknown error'}`);
|
|
222
|
+
}
|
|
223
|
+
},
|
|
224
|
+
});
|
|
225
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { UserError } from 'fastmcp';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getDriveClient } from '../../clients.js';
|
|
4
|
+
import { escapeDriveQuery } from '../../driveQueryUtils.js';
|
|
5
|
+
export function register(server) {
|
|
6
|
+
server.addTool({
|
|
7
|
+
name: 'listSpreadsheets',
|
|
8
|
+
description: 'Lists spreadsheets in your Drive, optionally filtered by name or content.',
|
|
9
|
+
parameters: z.strictObject({
|
|
10
|
+
maxResults: z
|
|
11
|
+
.number()
|
|
12
|
+
.int()
|
|
13
|
+
.min(1)
|
|
14
|
+
.max(100)
|
|
15
|
+
.optional()
|
|
16
|
+
.default(20)
|
|
17
|
+
.describe('Maximum number of spreadsheets to return (1-100).'),
|
|
18
|
+
query: z
|
|
19
|
+
.string()
|
|
20
|
+
.optional()
|
|
21
|
+
.describe('Search query to filter spreadsheets by name or content.'),
|
|
22
|
+
orderBy: z
|
|
23
|
+
.enum(['name', 'modifiedTime', 'createdTime'])
|
|
24
|
+
.optional()
|
|
25
|
+
.default('modifiedTime')
|
|
26
|
+
.describe('Sort order for results.'),
|
|
27
|
+
}),
|
|
28
|
+
execute: async (args, { log }) => {
|
|
29
|
+
const drive = await getDriveClient();
|
|
30
|
+
log.info(`Listing Google Sheets. Query: ${args.query || 'none'}, Max: ${args.maxResults}, Order: ${args.orderBy}`);
|
|
31
|
+
try {
|
|
32
|
+
// Build the query string for Google Drive API
|
|
33
|
+
let queryString = "mimeType='application/vnd.google-apps.spreadsheet' and trashed=false";
|
|
34
|
+
if (args.query) {
|
|
35
|
+
const escapedQuery = escapeDriveQuery(args.query);
|
|
36
|
+
queryString += ` and (name contains '${escapedQuery}' or fullText contains '${escapedQuery}')`;
|
|
37
|
+
}
|
|
38
|
+
const response = await drive.files.list({
|
|
39
|
+
q: queryString,
|
|
40
|
+
pageSize: args.maxResults,
|
|
41
|
+
// Drive rejects `orderBy` when `q` contains a `fullText` clause
|
|
42
|
+
// ("Sorting is not supported for queries with fullText terms").
|
|
43
|
+
...(args.query ? {} : { orderBy: args.orderBy === 'name' ? 'name' : args.orderBy }),
|
|
44
|
+
fields: 'files(id,name,modifiedTime,createdTime,size,webViewLink,owners(displayName,emailAddress))',
|
|
45
|
+
supportsAllDrives: true,
|
|
46
|
+
includeItemsFromAllDrives: true,
|
|
47
|
+
});
|
|
48
|
+
const files = response.data.files || [];
|
|
49
|
+
const spreadsheets = files.map((file) => ({
|
|
50
|
+
id: file.id,
|
|
51
|
+
name: file.name,
|
|
52
|
+
modifiedTime: file.modifiedTime,
|
|
53
|
+
owner: file.owners?.[0]?.displayName || null,
|
|
54
|
+
url: file.webViewLink,
|
|
55
|
+
}));
|
|
56
|
+
return JSON.stringify({ spreadsheets }, null, 2);
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
log.error(`Error listing Google Sheets: ${error.message || error}`);
|
|
60
|
+
if (error.code === 403)
|
|
61
|
+
throw new UserError('Permission denied. Make sure you have granted Google Drive access to the application.');
|
|
62
|
+
throw new UserError(`Failed to list spreadsheets: ${error.message || 'Unknown error'}`);
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
}
|