import type { FastMCP } from 'fastmcp'; import { UserError } from 'fastmcp'; import { z } from 'zod'; import { drive_v3, docs_v1 } from 'googleapis'; import { getDriveClient, getDocsClient } from '../../clients.js'; export function register(server: FastMCP) { server.addTool({ name: 'createDocumentFromTemplate', description: 'Creates a new document by copying an existing template and optionally replacing placeholder text. Provide key-value pairs in the replacements parameter to substitute template variables.', parameters: z.strictObject({ templateId: z.string().describe('ID of the template document to copy from.'), newTitle: z.string().min(1).describe('Title for the new document.'), parentFolderId: z .string() .optional() .describe( 'ID of folder where document should be created. If not provided, creates in Drive root.' ), replacements: z .record(z.string()) .optional() .describe( 'Key-value pairs for text replacements in the template (e.g., {"{{NAME}}": "John Doe", "{{DATE}}": "2024-01-01"}).' ), }), execute: async (args, { log }) => { const drive = await getDriveClient(); log.info(`Creating document from template ${args.templateId} with title "${args.newTitle}"`); try { // First copy the template const copyMetadata: drive_v3.Schema$File = { name: args.newTitle, }; if (args.parentFolderId) { copyMetadata.parents = [args.parentFolderId]; } const response = await drive.files.copy({ fileId: args.templateId, requestBody: copyMetadata, fields: 'id,name,webViewLink', supportsAllDrives: true, }); const document = response.data; let result = `Successfully created document "${document.name}" from template (ID: ${document.id})\nView Link: ${document.webViewLink}`; // Apply text replacements if provided if (args.replacements && Object.keys(args.replacements).length > 0) { try { const docs = await getDocsClient(); const requests: docs_v1.Schema$Request[] = []; // Create replace requests for each replacement for (const [searchText, replaceText] of Object.entries(args.replacements)) { requests.push({ replaceAllText: { containsText: { text: searchText, matchCase: false, }, replaceText: replaceText, }, }); } if (requests.length > 0) { await docs.documents.batchUpdate({ documentId: document.id!, requestBody: { requests }, }); const replacementCount = Object.keys(args.replacements).length; result += `\n\nApplied ${replacementCount} text replacement${replacementCount !== 1 ? 's' : ''} to the document.`; } } catch (replacementError: any) { log.warn( `Document created but failed to apply replacements: ${replacementError.message}` ); result += `\n\nDocument created but failed to apply text replacements. You can make changes manually.`; } } return result; } catch (error: any) { log.error(`Error creating document from template: ${error.message || error}`); if (error.code === 404) throw new UserError('Template document or parent folder not found. Check the IDs.'); if (error.code === 403) throw new UserError( 'Permission denied. Make sure you have read access to the template and write access to the destination folder.' ); throw new UserError( `Failed to create document from template: ${error.message || 'Unknown error'}` ); } }, }); }