Skip to main content
Glama
document-batch-update.ts7.59 kB
import {z} from 'zod'; import type {McpServer} from '@modelcontextprotocol/sdk/server/mcp.js'; import type {Config} from './types.js'; import {makeDocsApiCall} from '../utils/docs-api.js'; import {jsonResult} from '../utils/response.js'; // Location specification for insertions const locationSchema = z.object({ index: z.number().describe('The zero-based index in the document'), segmentId: z.string().optional().describe('The ID of the header, footer, or footnote. Empty for body.'), tabId: z.string().optional().describe('The tab ID containing the location'), }); // Range specification for modifications const rangeSchema = z.object({ startIndex: z.number().describe('The zero-based start index'), endIndex: z.number().describe('The zero-based end index (exclusive)'), segmentId: z.string().optional().describe('The ID of the header, footer, or footnote. Empty for body.'), tabId: z.string().optional().describe('The tab ID containing the range'), }); // SubstringMatchCriteria for find/replace const substringMatchCriteriaSchema = z.object({ text: z.string().describe('The text to search for'), matchCase: z.boolean().optional().describe('Whether the search is case-sensitive'), }); // Dimension specification for tables const dimensionSchema = z.object({ magnitude: z.number().describe('The magnitude'), unit: z.enum(['UNIT_UNSPECIFIED', 'PT']).default('PT').describe('The unit of measurement'), }); // Table cell location const tableCellLocationSchema = z.object({ tableStartLocation: locationSchema.describe('Location where the table begins'), rowIndex: z.number().describe('The zero-based row index'), columnIndex: z.number().describe('The zero-based column index'), }); // Individual request types const insertTextRequestSchema = z.object({ insertText: z.object({ text: z.string().describe('The text to insert'), location: locationSchema.optional().describe('Insert at this location'), endOfSegmentLocation: z.object({ segmentId: z.string().optional(), tabId: z.string().optional(), }).optional().describe('Insert at end of segment'), }), }); const deleteContentRangeRequestSchema = z.object({ deleteContentRange: z.object({ range: rangeSchema.describe('The range to delete'), }), }); const replaceAllTextRequestSchema = z.object({ replaceAllText: z.object({ containsText: substringMatchCriteriaSchema.describe('Text to find'), replaceText: z.string().describe('Replacement text'), tabsCriteria: z.object({ tabIds: z.array(z.string()).optional(), }).optional().describe('Which tabs to search'), }), }); const insertInlineImageRequestSchema = z.object({ insertInlineImage: z.object({ uri: z.string().describe('The image URI'), location: locationSchema.optional().describe('Insert at this location'), endOfSegmentLocation: z.object({ segmentId: z.string().optional(), tabId: z.string().optional(), }).optional().describe('Insert at end of segment'), objectSize: z.object({ height: dimensionSchema.optional(), width: dimensionSchema.optional(), }).optional().describe('The size of the image'), }), }); const insertTableRequestSchema = z.object({ insertTable: z.object({ rows: z.number().describe('Number of rows'), columns: z.number().describe('Number of columns'), location: locationSchema.optional().describe('Insert at this location'), endOfSegmentLocation: z.object({ segmentId: z.string().optional(), tabId: z.string().optional(), }).optional().describe('Insert at end of segment'), }), }); const insertTableRowRequestSchema = z.object({ insertTableRow: z.object({ tableCellLocation: tableCellLocationSchema.describe('Reference cell location'), insertBelow: z.boolean().optional().describe('Insert below the reference row'), }), }); const insertTableColumnRequestSchema = z.object({ insertTableColumn: z.object({ tableCellLocation: tableCellLocationSchema.describe('Reference cell location'), insertRight: z.boolean().optional().describe('Insert to the right of the reference column'), }), }); const deleteTableRowRequestSchema = z.object({ deleteTableRow: z.object({ tableCellLocation: tableCellLocationSchema.describe('Cell in row to delete'), }), }); const deleteTableColumnRequestSchema = z.object({ deleteTableColumn: z.object({ tableCellLocation: tableCellLocationSchema.describe('Cell in column to delete'), }), }); const insertPageBreakRequestSchema = z.object({ insertPageBreak: z.object({ location: locationSchema.optional().describe('Insert at this location'), endOfSegmentLocation: z.object({ segmentId: z.string().optional(), tabId: z.string().optional(), }).optional().describe('Insert at end of segment'), }), }); const createNamedRangeRequestSchema = z.object({ createNamedRange: z.object({ name: z.string().describe('Name for the range'), range: rangeSchema.describe('The range to name'), }), }); const deleteNamedRangeRequestSchema = z.object({ deleteNamedRange: z.object({ namedRangeId: z.string().optional().describe('ID of named range to delete'), name: z.string().optional().describe('Name of range to delete'), }), }); const createParagraphBulletsRequestSchema = z.object({ createParagraphBullets: z.object({ range: rangeSchema.describe('Range of paragraphs to bullet'), bulletPreset: z.enum([ 'BULLET_DISC_CIRCLE_SQUARE', 'BULLET_DIAMONDX_ARROW3D_SQUARE', 'BULLET_CHECKBOX', 'BULLET_ARROW_DIAMOND_DISC', 'BULLET_STAR_CIRCLE_SQUARE', 'BULLET_ARROW3D_CIRCLE_SQUARE', 'BULLET_LEFTTRIANGLE_DIAMOND_DISC', 'NUMBERED_DECIMAL_ALPHA_ROMAN', 'NUMBERED_DECIMAL_ALPHA_ROMAN_PARENS', 'NUMBERED_DECIMAL_NESTED', 'NUMBERED_UPPERALPHA_ALPHA_ROMAN', 'NUMBERED_UPPERROMAN_UPPERALPHA_DECIMAL', 'NUMBERED_ZERODECIMAL_ALPHA_ROMAN', ]).optional().describe('The bullet preset'), }), }); const deleteParagraphBulletsRequestSchema = z.object({ deleteParagraphBullets: z.object({ range: rangeSchema.describe('Range of paragraphs to remove bullets from'), }), }); // Union of all request types const requestSchema = z.union([ insertTextRequestSchema, deleteContentRangeRequestSchema, replaceAllTextRequestSchema, insertInlineImageRequestSchema, insertTableRequestSchema, insertTableRowRequestSchema, insertTableColumnRequestSchema, deleteTableRowRequestSchema, deleteTableColumnRequestSchema, insertPageBreakRequestSchema, createNamedRangeRequestSchema, deleteNamedRangeRequestSchema, createParagraphBulletsRequestSchema, deleteParagraphBulletsRequestSchema, ]); const inputSchema = { documentId: z.string().describe('The ID of the document to update'), requests: z.array(requestSchema).describe('The list of update requests to apply. All requests in a batch are applied atomically.'), }; const outputSchema = z.object({ documentId: z.string(), replies: z.array(z.unknown()).optional(), writeControl: z.object({ requiredRevisionId: z.string().optional(), }).optional(), }).passthrough(); export function registerDocumentBatchUpdate(server: McpServer, config: Config): void { server.registerTool( 'document_batch_update', { title: 'Batch update document', description: 'Apply one or more updates to a Google Doc. Supports inserting/deleting text, find/replace, inserting images and tables, managing bullets, and more. All requests are applied atomically - if any fails, none are applied.', inputSchema, outputSchema, }, async ({documentId, requests}) => { const result = await makeDocsApiCall('POST', `/documents/${documentId}:batchUpdate`, config.token, {requests}); return jsonResult(outputSchema.parse(result)); }, ); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/domdomegg/google-docs-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server