Spaces:
Sleeping
Sleeping
File size: 5,930 Bytes
7dc28be | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 | import type { FastMCP } from 'fastmcp';
import { UserError } from 'fastmcp';
import { z } from 'zod';
import { getSheetsClient } from '../../clients.js';
import * as SheetsHelpers from '../../googleSheetsApiHelpers.js';
export function register(server: FastMCP) {
server.addTool({
name: 'formatCells',
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'.",
parameters: z
.object({
spreadsheetId: z
.string()
.describe(
'The spreadsheet ID — the long string between /d/ and /edit in a Google Sheets URL.'
),
range: z
.string()
.describe(
'A1 notation range to format. Examples: "Sheet1!A1:D1", "1:1" (entire row 1), "A:A" (entire column A), "B2:E10".'
),
bold: z.boolean().optional().describe('Apply bold text formatting.'),
italic: z.boolean().optional().describe('Apply italic text formatting.'),
fontSize: z.number().min(1).optional().describe('Font size in points.'),
foregroundColor: z.string().optional().describe('Text color as hex (e.g., "#FF0000").'),
backgroundColor: z
.string()
.optional()
.describe('Cell background color as hex (e.g., "#D9EAD3").'),
horizontalAlignment: z
.enum(['LEFT', 'CENTER', 'RIGHT'])
.optional()
.describe('Horizontal text alignment.'),
verticalAlignment: z
.enum(['TOP', 'MIDDLE', 'BOTTOM'])
.optional()
.describe('Vertical text alignment within the cell.'),
wrapStrategy: z
.enum(['OVERFLOW_CELL', 'CLIP', 'WRAP'])
.optional()
.describe(
'Text wrap strategy: WRAP wraps to multiple lines, CLIP truncates, OVERFLOW_CELL overflows into adjacent cells.'
),
numberFormat: z
.object({
type: z
.enum([
'TEXT',
'NUMBER',
'PERCENT',
'CURRENCY',
'DATE',
'TIME',
'DATE_TIME',
'SCIENTIFIC',
])
.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.'
),
pattern: z
.string()
.optional()
.describe(
'Optional custom format pattern (e.g., "0.00", "#,##0", "yyyy-MM-dd"). If omitted, the default pattern for the type is used.'
),
})
.optional()
.describe(
'Controls how cell values are displayed. Useful for clearing date formatting (set type to "TEXT") or applying a custom number pattern.'
),
})
.refine(
(data) =>
data.bold !== undefined ||
data.italic !== undefined ||
data.fontSize !== undefined ||
data.foregroundColor !== undefined ||
data.backgroundColor !== undefined ||
data.horizontalAlignment !== undefined ||
data.verticalAlignment !== undefined ||
data.wrapStrategy !== undefined ||
data.numberFormat !== undefined,
{ message: 'At least one formatting option must be provided.' }
),
execute: async (args, { log }) => {
const sheets = await getSheetsClient();
log.info(`Formatting cells in range "${args.range}" of spreadsheet ${args.spreadsheetId}`);
try {
// Build the format object expected by the helper
const format: Parameters<typeof SheetsHelpers.formatCells>[3] = {};
if (args.backgroundColor) {
const rgb = SheetsHelpers.hexToRgb(args.backgroundColor);
if (!rgb) throw new UserError(`Invalid background color: "${args.backgroundColor}".`);
format.backgroundColor = rgb;
}
const hasTextFormat =
args.bold !== undefined ||
args.italic !== undefined ||
args.fontSize !== undefined ||
args.foregroundColor !== undefined;
if (hasTextFormat) {
format.textFormat = {};
if (args.bold !== undefined) format.textFormat.bold = args.bold;
if (args.italic !== undefined) format.textFormat.italic = args.italic;
if (args.fontSize !== undefined) format.textFormat.fontSize = args.fontSize;
if (args.foregroundColor) {
const rgb = SheetsHelpers.hexToRgb(args.foregroundColor);
if (!rgb) throw new UserError(`Invalid foreground color: "${args.foregroundColor}".`);
format.textFormat.foregroundColor = rgb;
}
}
if (args.horizontalAlignment) {
format.horizontalAlignment = args.horizontalAlignment;
}
if (args.verticalAlignment) {
format.verticalAlignment = args.verticalAlignment;
}
if (args.wrapStrategy) {
(format as any).wrapStrategy = args.wrapStrategy;
}
if (args.numberFormat) {
format.numberFormat = args.numberFormat;
}
await SheetsHelpers.formatCells(sheets, args.spreadsheetId, args.range, format);
return `Successfully applied formatting to range "${args.range}".`;
} catch (error: any) {
log.error(`Error formatting cells: ${error.message || error}`);
if (error instanceof UserError) throw error;
throw new UserError(`Failed to format cells: ${error.message || 'Unknown error'}`);
}
},
});
}
|