google-tools-mcp 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.
Files changed (95) hide show
  1. package/dist/auth.js +275 -0
  2. package/dist/cachedToolsList.js +52 -0
  3. package/dist/clients.js +97 -0
  4. package/dist/googleDocsApiHelpers.js +883 -0
  5. package/dist/googleSheetsApiHelpers.js +808 -0
  6. package/dist/helpers.js +210 -0
  7. package/dist/index.js +55 -0
  8. package/dist/logger.js +45 -0
  9. package/dist/markdown-transformer/docsToMarkdown.js +259 -0
  10. package/dist/markdown-transformer/index.js +126 -0
  11. package/dist/markdown-transformer/markdownToDocs.js +834 -0
  12. package/dist/tools/docs/addTab.js +92 -0
  13. package/dist/tools/docs/appendToGoogleDoc.js +81 -0
  14. package/dist/tools/docs/comments/addComment.js +83 -0
  15. package/dist/tools/docs/comments/deleteComment.js +30 -0
  16. package/dist/tools/docs/comments/getComment.js +45 -0
  17. package/dist/tools/docs/comments/index.js +14 -0
  18. package/dist/tools/docs/comments/listComments.js +43 -0
  19. package/dist/tools/docs/comments/replyToComment.js +35 -0
  20. package/dist/tools/docs/comments/resolveComment.js +55 -0
  21. package/dist/tools/docs/deleteRange.js +72 -0
  22. package/dist/tools/docs/findAndReplace.js +54 -0
  23. package/dist/tools/docs/formatting/applyParagraphStyle.js +83 -0
  24. package/dist/tools/docs/formatting/applyTextStyle.js +49 -0
  25. package/dist/tools/docs/formatting/index.js +6 -0
  26. package/dist/tools/docs/index.js +38 -0
  27. package/dist/tools/docs/insertImage.js +122 -0
  28. package/dist/tools/docs/insertPageBreak.js +58 -0
  29. package/dist/tools/docs/insertTable.js +53 -0
  30. package/dist/tools/docs/insertTableWithData.js +135 -0
  31. package/dist/tools/docs/insertText.js +61 -0
  32. package/dist/tools/docs/listDocumentTabs.js +60 -0
  33. package/dist/tools/docs/modifyText.js +158 -0
  34. package/dist/tools/docs/readGoogleDoc.js +165 -0
  35. package/dist/tools/docs/renameTab.js +61 -0
  36. package/dist/tools/drive/copyFile.js +63 -0
  37. package/dist/tools/drive/createDocument.js +89 -0
  38. package/dist/tools/drive/createFolder.js +48 -0
  39. package/dist/tools/drive/createFromTemplate.js +82 -0
  40. package/dist/tools/drive/deleteFile.js +72 -0
  41. package/dist/tools/drive/getDocumentInfo.js +48 -0
  42. package/dist/tools/drive/getFolderInfo.js +48 -0
  43. package/dist/tools/drive/index.js +30 -0
  44. package/dist/tools/drive/listFolderContents.js +82 -0
  45. package/dist/tools/drive/listGoogleDocs.js +67 -0
  46. package/dist/tools/drive/listSharedDrives.js +52 -0
  47. package/dist/tools/drive/listSharedWithMe.js +97 -0
  48. package/dist/tools/drive/moveFile.js +54 -0
  49. package/dist/tools/drive/renameFile.js +39 -0
  50. package/dist/tools/drive/searchGoogleDocs.js +98 -0
  51. package/dist/tools/extras/index.js +7 -0
  52. package/dist/tools/extras/readFile.js +82 -0
  53. package/dist/tools/extras/searchFileContents.js +81 -0
  54. package/dist/tools/gmail/drafts.js +165 -0
  55. package/dist/tools/gmail/labels.js +103 -0
  56. package/dist/tools/gmail/messages.js +448 -0
  57. package/dist/tools/gmail/settings.js +528 -0
  58. package/dist/tools/gmail/threads.js +145 -0
  59. package/dist/tools/index.js +163 -0
  60. package/dist/tools/sheets/addConditionalFormatting.js +143 -0
  61. package/dist/tools/sheets/addSpreadsheetSheet.js +34 -0
  62. package/dist/tools/sheets/appendSpreadsheetRows.js +43 -0
  63. package/dist/tools/sheets/appendTableRows.js +50 -0
  64. package/dist/tools/sheets/autoResizeColumns.js +67 -0
  65. package/dist/tools/sheets/batchWrite.js +59 -0
  66. package/dist/tools/sheets/clearSpreadsheetRange.js +31 -0
  67. package/dist/tools/sheets/copyFormatting.js +59 -0
  68. package/dist/tools/sheets/createSpreadsheet.js +71 -0
  69. package/dist/tools/sheets/createTable.js +120 -0
  70. package/dist/tools/sheets/deleteChart.js +41 -0
  71. package/dist/tools/sheets/deleteSheet.js +43 -0
  72. package/dist/tools/sheets/deleteTable.js +56 -0
  73. package/dist/tools/sheets/duplicateSheet.js +53 -0
  74. package/dist/tools/sheets/formatCells.js +106 -0
  75. package/dist/tools/sheets/freezeRowsAndColumns.js +58 -0
  76. package/dist/tools/sheets/getSpreadsheetInfo.js +44 -0
  77. package/dist/tools/sheets/getTable.js +48 -0
  78. package/dist/tools/sheets/groupRows.js +62 -0
  79. package/dist/tools/sheets/index.js +66 -0
  80. package/dist/tools/sheets/insertChart.js +225 -0
  81. package/dist/tools/sheets/listGoogleSheets.js +62 -0
  82. package/dist/tools/sheets/listTables.js +55 -0
  83. package/dist/tools/sheets/readCellFormat.js +143 -0
  84. package/dist/tools/sheets/readSpreadsheet.js +36 -0
  85. package/dist/tools/sheets/renameSheet.js +48 -0
  86. package/dist/tools/sheets/setColumnWidths.js +43 -0
  87. package/dist/tools/sheets/setDropdownValidation.js +51 -0
  88. package/dist/tools/sheets/ungroupAllRows.js +66 -0
  89. package/dist/tools/sheets/updateTableRange.js +51 -0
  90. package/dist/tools/sheets/writeSpreadsheet.js +43 -0
  91. package/dist/tools/utils/appendMarkdownToGoogleDoc.js +93 -0
  92. package/dist/tools/utils/index.js +6 -0
  93. package/dist/tools/utils/replaceDocumentWithMarkdown.js +154 -0
  94. package/dist/types.js +186 -0
  95. package/package.json +30 -0
@@ -0,0 +1,71 @@
1
+ import { UserError } from 'fastmcp';
2
+ import { z } from 'zod';
3
+ import { getDriveClient, getSheetsClient } from '../../clients.js';
4
+ import * as SheetsHelpers from '../../googleSheetsApiHelpers.js';
5
+ export function register(server) {
6
+ server.addTool({
7
+ name: 'createSpreadsheet',
8
+ description: 'Creates a new spreadsheet. Optionally places it in a specific folder and populates it with initial data.',
9
+ parameters: z.object({
10
+ title: z.string().min(1).describe('Title for the new spreadsheet.'),
11
+ parentFolderId: z
12
+ .string()
13
+ .optional()
14
+ .describe('ID of folder where spreadsheet should be created. If not provided, creates in Drive root.'),
15
+ initialData: z
16
+ .array(z.array(z.any()))
17
+ .optional()
18
+ .describe('Optional initial data to populate in the first sheet. Each inner array represents a row.'),
19
+ }),
20
+ execute: async (args, { log }) => {
21
+ const drive = await getDriveClient();
22
+ const sheets = await getSheetsClient();
23
+ log.info(`Creating new spreadsheet "${args.title}"`);
24
+ try {
25
+ // Create the spreadsheet file in Drive
26
+ const spreadsheetMetadata = {
27
+ name: args.title,
28
+ mimeType: 'application/vnd.google-apps.spreadsheet',
29
+ };
30
+ if (args.parentFolderId) {
31
+ spreadsheetMetadata.parents = [args.parentFolderId];
32
+ }
33
+ const driveResponse = await drive.files.create({
34
+ requestBody: spreadsheetMetadata,
35
+ fields: 'id,name,webViewLink',
36
+ supportsAllDrives: true,
37
+ });
38
+ const spreadsheetId = driveResponse.data.id;
39
+ if (!spreadsheetId) {
40
+ throw new UserError('Failed to create spreadsheet - no ID returned.');
41
+ }
42
+ let initialDataStatus;
43
+ // Add initial data if provided
44
+ if (args.initialData && args.initialData.length > 0) {
45
+ try {
46
+ await SheetsHelpers.writeRange(sheets, spreadsheetId, 'A1', args.initialData, 'USER_ENTERED');
47
+ initialDataStatus = 'added';
48
+ }
49
+ catch (contentError) {
50
+ log.warn(`Spreadsheet created but failed to add initial data: ${contentError.message}`);
51
+ initialDataStatus = 'failed';
52
+ }
53
+ }
54
+ return JSON.stringify({
55
+ id: spreadsheetId,
56
+ name: driveResponse.data.name,
57
+ url: driveResponse.data.webViewLink,
58
+ ...(initialDataStatus ? { initialData: initialDataStatus } : {}),
59
+ }, null, 2);
60
+ }
61
+ catch (error) {
62
+ log.error(`Error creating spreadsheet: ${error.message || error}`);
63
+ if (error.code === 404)
64
+ throw new UserError('Parent folder not found. Check the folder ID.');
65
+ if (error.code === 403)
66
+ throw new UserError('Permission denied. Make sure you have write access to the destination folder.');
67
+ throw new UserError(`Failed to create spreadsheet: ${error.message || 'Unknown error'}`);
68
+ }
69
+ },
70
+ });
71
+ }
@@ -0,0 +1,120 @@
1
+ import { UserError } from 'fastmcp';
2
+ import { z } from 'zod';
3
+ import { getSheetsClient } from '../../clients.js';
4
+ import * as SheetsHelpers from '../../googleSheetsApiHelpers.js';
5
+ // Column type enum matching Google Sheets API
6
+ const ColumnTypeSchema = z.enum([
7
+ 'TEXT',
8
+ 'NUMBER',
9
+ 'DATE',
10
+ 'DROPDOWN',
11
+ 'CHECKBOX',
12
+ 'PERCENT',
13
+ 'CURRENCY',
14
+ ]);
15
+ export function register(server) {
16
+ server.addTool({
17
+ name: 'createTable',
18
+ description: 'Creates a new named table with specific column types. Tables provide structured data with typed columns, automatic formatting, and special features like dropdown validation.',
19
+ parameters: z
20
+ .object({
21
+ spreadsheetId: z
22
+ .string()
23
+ .describe('The spreadsheet ID — the long string between /d/ and /edit in a Google Sheets URL.'),
24
+ name: z
25
+ .string()
26
+ .min(1)
27
+ .describe('Unique name for the table within this spreadsheet. Table names must be unique.'),
28
+ range: z
29
+ .string()
30
+ .describe('A1 notation range for the table (e.g., "Sheet1!A1:E10"). Must be within existing sheet boundaries.'),
31
+ columns: z
32
+ .array(z.object({
33
+ columnName: z.string().min(1).describe('Display name for the column header.'),
34
+ columnType: ColumnTypeSchema.optional().describe('Data type for the column (default: TEXT).'),
35
+ dropdownValues: z
36
+ .array(z.string())
37
+ .optional()
38
+ .describe('Required for DROPDOWN type: list of dropdown options.'),
39
+ }))
40
+ .optional()
41
+ .describe('Column definitions. If not specified, columns are auto-detected from first row.'),
42
+ hasHeaderRow: z
43
+ .boolean()
44
+ .optional()
45
+ .default(true)
46
+ .describe('Whether the table has a header row (default: true).'),
47
+ hasFooterRow: z
48
+ .boolean()
49
+ .optional()
50
+ .default(false)
51
+ .describe('Whether the table should have a footer row (default: false).'),
52
+ })
53
+ .refine((data) => {
54
+ // Validate dropdown columns have values
55
+ if (data.columns) {
56
+ for (const col of data.columns) {
57
+ if (col.columnType === 'DROPDOWN' &&
58
+ (!col.dropdownValues || col.dropdownValues.length === 0)) {
59
+ return false;
60
+ }
61
+ }
62
+ }
63
+ return true;
64
+ }, {
65
+ message: 'DROPDOWN column type requires dropdownValues to be specified with at least one option.',
66
+ path: ['columns'],
67
+ }),
68
+ execute: async (args, { log }) => {
69
+ const sheets = await getSheetsClient();
70
+ log.info(`Creating table "${args.name}" in spreadsheet: ${args.spreadsheetId}`);
71
+ try {
72
+ // Parse the range to get sheet name and grid range
73
+ const { sheetName, a1Range } = SheetsHelpers.parseRange(args.range);
74
+ const sheetId = await SheetsHelpers.resolveSheetId(sheets, args.spreadsheetId, sheetName);
75
+ const gridRange = SheetsHelpers.parseA1ToGridRange(a1Range, sheetId);
76
+ // Build column properties if provided
77
+ let columnProperties;
78
+ if (args.columns && args.columns.length > 0) {
79
+ columnProperties = args.columns.map((col, index) => {
80
+ const prop = {
81
+ columnIndex: index,
82
+ columnName: col.columnName,
83
+ };
84
+ // Add data validation for DROPDOWN type
85
+ if (col.columnType === 'DROPDOWN' &&
86
+ col.dropdownValues &&
87
+ col.dropdownValues.length > 0) {
88
+ prop.dataValidationRule = {
89
+ condition: {
90
+ type: 'ONE_OF_LIST',
91
+ values: col.dropdownValues.map((v) => ({ userEnteredValue: v })),
92
+ },
93
+ };
94
+ }
95
+ return prop;
96
+ });
97
+ }
98
+ // Create the table
99
+ const table = await SheetsHelpers.createTableHelper(sheets, args.spreadsheetId, {
100
+ name: args.name,
101
+ range: gridRange,
102
+ columnProperties,
103
+ });
104
+ return JSON.stringify({
105
+ tableId: table.tableId,
106
+ name: table.name,
107
+ range: args.range,
108
+ columnCount: table.columnProperties?.length || 0,
109
+ message: `Table "${args.name}" created successfully.`,
110
+ }, null, 2);
111
+ }
112
+ catch (error) {
113
+ log.error(`Error creating table: ${error.message || error}`);
114
+ if (error instanceof UserError)
115
+ throw error;
116
+ throw new UserError(`Failed to create table: ${error.message || 'Unknown error'}`);
117
+ }
118
+ },
119
+ });
120
+ }
@@ -0,0 +1,41 @@
1
+ import { UserError } from 'fastmcp';
2
+ import { z } from 'zod';
3
+ import { getSheetsClient } from '../../clients.js';
4
+ export function register(server) {
5
+ server.addTool({
6
+ name: 'deleteChart',
7
+ description: 'Deletes a chart from a Google Spreadsheet by chart ID. ' +
8
+ 'The chart ID is returned when a chart is created with insertChart.',
9
+ parameters: z.object({
10
+ spreadsheetId: z
11
+ .string()
12
+ .describe('The spreadsheet ID — the long string between /d/ and /edit in a Google Sheets URL.'),
13
+ chartId: z.number().int().describe('The numeric chart ID to delete.'),
14
+ }),
15
+ execute: async (args, { log }) => {
16
+ const sheets = await getSheetsClient();
17
+ log.info(`Deleting chart ${args.chartId} from spreadsheet ${args.spreadsheetId}`);
18
+ try {
19
+ await sheets.spreadsheets.batchUpdate({
20
+ spreadsheetId: args.spreadsheetId,
21
+ requestBody: {
22
+ requests: [
23
+ {
24
+ deleteEmbeddedObject: {
25
+ objectId: args.chartId,
26
+ },
27
+ },
28
+ ],
29
+ },
30
+ });
31
+ return `Chart ${args.chartId} deleted successfully.`;
32
+ }
33
+ catch (error) {
34
+ log.error(`Error deleting chart: ${error.message || error}`);
35
+ if (error instanceof UserError)
36
+ throw error;
37
+ throw new UserError(`Failed to delete chart: ${error.message || 'Unknown error'}`);
38
+ }
39
+ },
40
+ });
41
+ }
@@ -0,0 +1,43 @@
1
+ import { UserError } from 'fastmcp';
2
+ import { z } from 'zod';
3
+ import { getSheetsClient } from '../../clients.js';
4
+ export function register(server) {
5
+ server.addTool({
6
+ name: 'deleteSheet',
7
+ description: 'Deletes a sheet (tab) from a spreadsheet. Use getSpreadsheetInfo to find the numeric sheet ID.',
8
+ parameters: z.object({
9
+ spreadsheetId: z
10
+ .string()
11
+ .describe('The spreadsheet ID — the long string between /d/ and /edit in a Google Sheets URL.'),
12
+ sheetId: z
13
+ .number()
14
+ .int()
15
+ .describe('The numeric sheet ID to delete. Use getSpreadsheetInfo to find sheet IDs.'),
16
+ }),
17
+ execute: async (args, { log }) => {
18
+ const sheets = await getSheetsClient();
19
+ log.info(`Deleting sheet ID ${args.sheetId} from spreadsheet ${args.spreadsheetId}`);
20
+ try {
21
+ await sheets.spreadsheets.batchUpdate({
22
+ spreadsheetId: args.spreadsheetId,
23
+ requestBody: {
24
+ requests: [
25
+ {
26
+ deleteSheet: {
27
+ sheetId: args.sheetId,
28
+ },
29
+ },
30
+ ],
31
+ },
32
+ });
33
+ return `Successfully deleted sheet (ID: ${args.sheetId}) from spreadsheet.`;
34
+ }
35
+ catch (error) {
36
+ log.error(`Error deleting sheet in spreadsheet ${args.spreadsheetId}: ${error.message || error}`);
37
+ if (error instanceof UserError)
38
+ throw error;
39
+ throw new UserError(`Failed to delete sheet: ${error.message || 'Unknown error'}`);
40
+ }
41
+ },
42
+ });
43
+ }
@@ -0,0 +1,56 @@
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: 'deleteTable',
8
+ description: 'Deletes a table from a spreadsheet. By default, only removes the table object and formatting while keeping the cell data. Optionally clears the data as well.',
9
+ parameters: z.object({
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 to delete. Use listTables to see available tables.'),
16
+ deleteData: z
17
+ .boolean()
18
+ .optional()
19
+ .default(false)
20
+ .describe('If true, also clears the cell data in the table range. If false (default), only removes the table object and formatting.'),
21
+ }),
22
+ execute: async (args, { log }) => {
23
+ const sheets = await getSheetsClient();
24
+ log.info(`Deleting table "${args.tableIdentifier}" from spreadsheet: ${args.spreadsheetId}`);
25
+ try {
26
+ // First resolve the table to get its ID and range
27
+ const { table, sheetName } = await SheetsHelpers.resolveTableIdentifier(sheets, args.spreadsheetId, args.tableIdentifier);
28
+ // Delete the table
29
+ await SheetsHelpers.deleteTableHelper(sheets, args.spreadsheetId, table.tableId || '');
30
+ // If deleteData is true, clear the range
31
+ let clearedRange = null;
32
+ if (args.deleteData && table.range) {
33
+ const range = `${sheetName}!${SheetsHelpers.rowColToA1(table.range.startRowIndex || 0, table.range.startColumnIndex || 0)}:${SheetsHelpers.rowColToA1((table.range.endRowIndex || 1) - 1, (table.range.endColumnIndex || 1) - 1)}`;
34
+ await SheetsHelpers.clearRange(sheets, args.spreadsheetId, range);
35
+ clearedRange = range;
36
+ }
37
+ return JSON.stringify({
38
+ tableId: table.tableId,
39
+ name: table.name,
40
+ deleted: true,
41
+ dataCleared: args.deleteData,
42
+ clearedRange,
43
+ message: args.deleteData
44
+ ? `Table "${table.name}" deleted and data cleared.`
45
+ : `Table "${table.name}" deleted. Data preserved in range.`,
46
+ }, null, 2);
47
+ }
48
+ catch (error) {
49
+ log.error(`Error deleting table: ${error.message || error}`);
50
+ if (error instanceof UserError)
51
+ throw error;
52
+ throw new UserError(`Failed to delete table: ${error.message || 'Unknown error'}`);
53
+ }
54
+ },
55
+ });
56
+ }
@@ -0,0 +1,53 @@
1
+ import { UserError } from 'fastmcp';
2
+ import { z } from 'zod';
3
+ import { getSheetsClient } from '../../clients.js';
4
+ export function register(server) {
5
+ server.addTool({
6
+ name: 'duplicateSheet',
7
+ description: 'Duplicates a sheet (tab) within a spreadsheet, copying all values, formulas, formatting, validations, and conditional formatting. Use getSpreadsheetInfo to find the numeric sheet ID.',
8
+ parameters: z.object({
9
+ spreadsheetId: z
10
+ .string()
11
+ .describe('The spreadsheet ID — the long string between /d/ and /edit in a Google Sheets URL.'),
12
+ sheetId: z
13
+ .number()
14
+ .int()
15
+ .describe('The numeric sheet ID to duplicate. Use getSpreadsheetInfo to find sheet IDs.'),
16
+ newSheetName: z
17
+ .string()
18
+ .min(1)
19
+ .optional()
20
+ .describe('Name for the duplicated sheet. If omitted, Google auto-names it "Copy of <original>".'),
21
+ }),
22
+ execute: async (args, { log }) => {
23
+ const sheets = await getSheetsClient();
24
+ log.info(`Duplicating sheet ID ${args.sheetId} in spreadsheet ${args.spreadsheetId}${args.newSheetName ? ` as "${args.newSheetName}"` : ''}`);
25
+ try {
26
+ const response = await sheets.spreadsheets.batchUpdate({
27
+ spreadsheetId: args.spreadsheetId,
28
+ requestBody: {
29
+ requests: [
30
+ {
31
+ duplicateSheet: {
32
+ sourceSheetId: args.sheetId,
33
+ newSheetName: args.newSheetName,
34
+ },
35
+ },
36
+ ],
37
+ },
38
+ });
39
+ const duplicatedSheet = response.data.replies?.[0]?.duplicateSheet?.properties;
40
+ if (!duplicatedSheet) {
41
+ throw new UserError('Failed to duplicate sheet - no sheet properties returned.');
42
+ }
43
+ return `Successfully duplicated sheet as "${duplicatedSheet.title}" (Sheet ID: ${duplicatedSheet.sheetId}).`;
44
+ }
45
+ catch (error) {
46
+ log.error(`Error duplicating sheet in spreadsheet ${args.spreadsheetId}: ${error.message || error}`);
47
+ if (error instanceof UserError)
48
+ throw error;
49
+ throw new UserError(`Failed to duplicate sheet: ${error.message || 'Unknown error'}`);
50
+ }
51
+ },
52
+ });
53
+ }
@@ -0,0 +1,106 @@
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: 'formatCells',
8
+ description: "Applies formatting to a range of cells in a spreadsheet. Supports bold, italic, font size, text color, background color, alignment, and number format. Use range '1:1' to format an entire header row, 'A:A' for an entire column, or 'A1:D1' for specific cells. Use numberFormat to control how values are displayed (e.g. as numbers, text, dates) or to clear a format by setting type to 'TEXT'.",
9
+ parameters: z
10
+ .object({
11
+ spreadsheetId: z
12
+ .string()
13
+ .describe('The spreadsheet ID — the long string between /d/ and /edit in a Google Sheets URL.'),
14
+ range: z
15
+ .string()
16
+ .describe('A1 notation range to format. Examples: "Sheet1!A1:D1", "1:1" (entire row 1), "A:A" (entire column A), "B2:E10".'),
17
+ bold: z.boolean().optional().describe('Apply bold text formatting.'),
18
+ italic: z.boolean().optional().describe('Apply italic text formatting.'),
19
+ fontSize: z.number().min(1).optional().describe('Font size in points.'),
20
+ foregroundColor: z.string().optional().describe('Text color as hex (e.g., "#FF0000").'),
21
+ backgroundColor: z
22
+ .string()
23
+ .optional()
24
+ .describe('Cell background color as hex (e.g., "#D9EAD3").'),
25
+ horizontalAlignment: z
26
+ .enum(['LEFT', 'CENTER', 'RIGHT'])
27
+ .optional()
28
+ .describe('Horizontal text alignment.'),
29
+ numberFormat: z
30
+ .object({
31
+ type: z
32
+ .enum([
33
+ 'TEXT',
34
+ 'NUMBER',
35
+ 'PERCENT',
36
+ 'CURRENCY',
37
+ 'DATE',
38
+ 'TIME',
39
+ 'DATE_TIME',
40
+ 'SCIENTIFIC',
41
+ ])
42
+ .describe('Number format type. Use "TEXT" to treat cells as plain text (also clears any existing date/number format). Use "NUMBER" for general numeric display.'),
43
+ pattern: z
44
+ .string()
45
+ .optional()
46
+ .describe('Optional custom format pattern (e.g., "0.00", "#,##0", "yyyy-MM-dd"). If omitted, the default pattern for the type is used.'),
47
+ })
48
+ .optional()
49
+ .describe('Controls how cell values are displayed. Useful for clearing date formatting (set type to "TEXT") or applying a custom number pattern.'),
50
+ })
51
+ .refine((data) => data.bold !== undefined ||
52
+ data.italic !== undefined ||
53
+ data.fontSize !== undefined ||
54
+ data.foregroundColor !== undefined ||
55
+ data.backgroundColor !== undefined ||
56
+ data.horizontalAlignment !== undefined ||
57
+ data.numberFormat !== undefined, { message: 'At least one formatting option must be provided.' }),
58
+ execute: async (args, { log }) => {
59
+ const sheets = await getSheetsClient();
60
+ log.info(`Formatting cells in range "${args.range}" of spreadsheet ${args.spreadsheetId}`);
61
+ try {
62
+ // Build the format object expected by the helper
63
+ const format = {};
64
+ if (args.backgroundColor) {
65
+ const rgb = SheetsHelpers.hexToRgb(args.backgroundColor);
66
+ if (!rgb)
67
+ throw new UserError(`Invalid background color: "${args.backgroundColor}".`);
68
+ format.backgroundColor = rgb;
69
+ }
70
+ const hasTextFormat = args.bold !== undefined ||
71
+ args.italic !== undefined ||
72
+ args.fontSize !== undefined ||
73
+ args.foregroundColor !== undefined;
74
+ if (hasTextFormat) {
75
+ format.textFormat = {};
76
+ if (args.bold !== undefined)
77
+ format.textFormat.bold = args.bold;
78
+ if (args.italic !== undefined)
79
+ format.textFormat.italic = args.italic;
80
+ if (args.fontSize !== undefined)
81
+ format.textFormat.fontSize = args.fontSize;
82
+ if (args.foregroundColor) {
83
+ const rgb = SheetsHelpers.hexToRgb(args.foregroundColor);
84
+ if (!rgb)
85
+ throw new UserError(`Invalid foreground color: "${args.foregroundColor}".`);
86
+ format.textFormat.foregroundColor = rgb;
87
+ }
88
+ }
89
+ if (args.horizontalAlignment) {
90
+ format.horizontalAlignment = args.horizontalAlignment;
91
+ }
92
+ if (args.numberFormat) {
93
+ format.numberFormat = args.numberFormat;
94
+ }
95
+ await SheetsHelpers.formatCells(sheets, args.spreadsheetId, args.range, format);
96
+ return `Successfully applied formatting to range "${args.range}".`;
97
+ }
98
+ catch (error) {
99
+ log.error(`Error formatting cells: ${error.message || error}`);
100
+ if (error instanceof UserError)
101
+ throw error;
102
+ throw new UserError(`Failed to format cells: ${error.message || 'Unknown error'}`);
103
+ }
104
+ },
105
+ });
106
+ }
@@ -0,0 +1,58 @@
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: 'freezeRowsAndColumns',
8
+ description: 'Pins rows and/or columns so they stay visible when scrolling. Use frozenRows=1 to freeze a header row. Set a value to 0 to unfreeze.',
9
+ parameters: z
10
+ .object({
11
+ spreadsheetId: z
12
+ .string()
13
+ .describe('The spreadsheet ID — the long string between /d/ and /edit in a Google Sheets URL.'),
14
+ sheetName: z
15
+ .string()
16
+ .optional()
17
+ .describe('Name of the sheet/tab. Defaults to the first sheet if not provided.'),
18
+ frozenRows: z
19
+ .number()
20
+ .int()
21
+ .min(0)
22
+ .optional()
23
+ .describe('Number of rows to freeze from the top (e.g., 1 for a header row). Set to 0 to unfreeze rows.'),
24
+ frozenColumns: z
25
+ .number()
26
+ .int()
27
+ .min(0)
28
+ .optional()
29
+ .describe('Number of columns to freeze from the left. Set to 0 to unfreeze columns.'),
30
+ })
31
+ .refine((data) => data.frozenRows !== undefined || data.frozenColumns !== undefined, {
32
+ message: 'At least one of frozenRows or frozenColumns must be provided.',
33
+ }),
34
+ execute: async (args, { log }) => {
35
+ const sheets = await getSheetsClient();
36
+ log.info(`Freezing rows=${args.frozenRows ?? 'unchanged'}, cols=${args.frozenColumns ?? 'unchanged'} in spreadsheet ${args.spreadsheetId}`);
37
+ try {
38
+ await SheetsHelpers.freezeRowsAndColumns(sheets, args.spreadsheetId, args.sheetName, args.frozenRows, args.frozenColumns);
39
+ const parts = [];
40
+ if (args.frozenRows !== undefined) {
41
+ parts.push(args.frozenRows === 0 ? 'unfroze rows' : `froze top ${args.frozenRows} row(s)`);
42
+ }
43
+ if (args.frozenColumns !== undefined) {
44
+ parts.push(args.frozenColumns === 0
45
+ ? 'unfroze columns'
46
+ : `froze left ${args.frozenColumns} column(s)`);
47
+ }
48
+ return `Successfully ${parts.join(' and ')}.`;
49
+ }
50
+ catch (error) {
51
+ log.error(`Error freezing rows/columns: ${error.message || error}`);
52
+ if (error instanceof UserError)
53
+ throw error;
54
+ throw new UserError(`Failed to freeze rows/columns: ${error.message || 'Unknown error'}`);
55
+ }
56
+ },
57
+ });
58
+ }
@@ -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.object({
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.object({
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
+ }