import type { FastMCP } from 'fastmcp'; import { UserError } from 'fastmcp'; import { getDocsClient } from '../../../clients.js'; import { ApplyParagraphStyleToolParameters, ApplyParagraphStyleToolArgs, NotImplementedError, } from '../../../types.js'; import * as GDocsHelpers from '../../../googleDocsApiHelpers.js'; export function register(server: FastMCP) { server.addTool({ name: 'applyParagraphStyle', description: 'Applies paragraph-level formatting (alignment, spacing, heading styles) to paragraphs identified by a character range or by searching for text. Use namedStyleType to set heading levels (HEADING_1 through HEADING_6).', parameters: ApplyParagraphStyleToolParameters, execute: async (args: ApplyParagraphStyleToolArgs, { log }) => { const docs = await getDocsClient(); let startIndex: number | undefined; let endIndex: number | undefined; log.info( `Applying paragraph style to document ${args.documentId}${args.tabId ? ` (tab: ${args.tabId})` : ''}` ); log.info(`Style options: ${JSON.stringify(args.style)}`); log.info(`Target specification: ${JSON.stringify(args.target)}`); try { // STEP 1: Determine the target paragraph's range based on the targeting method if ('textToFind' in args.target) { // Find the text first log.info( `Finding text "${args.target.textToFind}" (instance ${args.target.matchInstance || 1})${args.tabId ? ` in tab ${args.tabId}` : ''}` ); const textRange = await GDocsHelpers.findTextRange( docs, args.documentId, args.target.textToFind, args.target.matchInstance || 1, args.tabId ); if (!textRange) { throw new UserError( `Could not find "${args.target.textToFind}" in the document${args.tabId ? ` (tab: ${args.tabId})` : ''}.` ); } log.info( `Found text at range ${textRange.startIndex}-${textRange.endIndex}, now locating containing paragraph` ); // Then find the paragraph containing this text const paragraphRange = await GDocsHelpers.getParagraphRange( docs, args.documentId, textRange.startIndex, args.tabId ); if (!paragraphRange) { throw new UserError(`Found the text but could not determine the paragraph boundaries.`); } startIndex = paragraphRange.startIndex; endIndex = paragraphRange.endIndex; log.info(`Text is contained within paragraph at range ${startIndex}-${endIndex}`); } else if ('indexWithinParagraph' in args.target) { // Find paragraph containing the specified index log.info( `Finding paragraph containing index ${args.target.indexWithinParagraph}${args.tabId ? ` in tab ${args.tabId}` : ''}` ); const paragraphRange = await GDocsHelpers.getParagraphRange( docs, args.documentId, args.target.indexWithinParagraph, args.tabId ); if (!paragraphRange) { throw new UserError( `Could not find paragraph containing index ${args.target.indexWithinParagraph}${args.tabId ? ` in tab ${args.tabId}` : ''}.` ); } startIndex = paragraphRange.startIndex; endIndex = paragraphRange.endIndex; log.info(`Located paragraph at range ${startIndex}-${endIndex}`); } else if ('startIndex' in args.target && 'endIndex' in args.target) { // Use directly provided range startIndex = args.target.startIndex; endIndex = args.target.endIndex; log.info(`Using provided paragraph range ${startIndex}-${endIndex}`); } // Verify that we have a valid range if (startIndex === undefined || endIndex === undefined) { throw new UserError( 'Could not determine target paragraph range from the provided information.' ); } if (endIndex <= startIndex) { throw new UserError( `Invalid paragraph range: end index (${endIndex}) must be greater than start index (${startIndex}).` ); } // STEP 2: Build and apply the paragraph style request log.info(`Building paragraph style request for range ${startIndex}-${endIndex}`); const requestInfo = GDocsHelpers.buildUpdateParagraphStyleRequest( startIndex, endIndex, args.style, args.tabId ); if (!requestInfo) { return 'No valid paragraph styling options were provided.'; } log.info(`Applying styles: ${requestInfo.fields.join(', ')}`); await GDocsHelpers.executeBatchUpdate(docs, args.documentId, [requestInfo.request]); return `Successfully applied paragraph styles (${requestInfo.fields.join(', ')}) to the paragraph${args.tabId ? ` in tab ${args.tabId}` : ''}.`; } catch (error: any) { // Detailed error logging log.error(`Error applying paragraph style in doc ${args.documentId}:`); log.error(error.stack || error.message || error); if (error instanceof UserError) throw error; if (error instanceof NotImplementedError) throw error; // Provide a more helpful error message throw new UserError(`Failed to apply paragraph style: ${error.message || 'Unknown error'}`); } }, }); }