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'}`);
}
},
});
}