google-docs-mcp / src /tools /utils /replaceRangeWithMarkdown.ts
iFightDucks's picture
Initial HF Space deploy: a-bonus/google-docs-mcp with HF metadata
7dc28be
import type { FastMCP } from 'fastmcp';
import { UserError } from 'fastmcp';
import { z } from 'zod';
import { getDocsClient } from '../../clients.js';
import { DocumentIdParameter, MarkdownConversionError } from '../../types.js';
import * as GDocsHelpers from '../../googleDocsApiHelpers.js';
import { insertMarkdown, formatInsertResult } from '../../markdown-transformer/index.js';
export function register(server: FastMCP) {
server.addTool({
name: 'replaceRangeWithMarkdown',
description:
"Replaces a specific character range in a document with formatted markdown content. Use readDocument with format='json' to determine the start and end indices of the content you want to replace. Supports headings, bold, italic, strikethrough, links, bullet/numbered lists, code blocks, horizontal rules, and tables.",
parameters: DocumentIdParameter.extend({
startIndex: z
.number()
.int()
.min(1)
.describe(
"1-based character index where the replacement range begins (inclusive). Use readDocument with format='json' to find the correct index."
),
endIndex: z
.number()
.int()
.min(1)
.describe(
"1-based character index where the replacement range ends (exclusive). Use readDocument with format='json' to find the correct index."
),
markdown: z
.string()
.min(1)
.describe('The markdown content to insert in place of the deleted range.'),
tabId: z
.string()
.optional()
.describe(
'The ID of the specific tab to modify. If not specified, modifies the first tab.'
),
}).refine((data) => data.endIndex > data.startIndex, {
message: 'endIndex must be greater than startIndex',
path: ['endIndex'],
}),
execute: async (args, { log }) => {
const docs = await getDocsClient();
log.info(
`Replacing range ${args.startIndex}-${args.endIndex} in doc ${args.documentId} with markdown (${args.markdown.length} chars)${args.tabId ? ` in tab ${args.tabId}` : ''}`
);
try {
// 1. Delete the existing content in the specified range
const deleteRange: any = {
startIndex: args.startIndex,
endIndex: args.endIndex,
};
if (args.tabId) {
deleteRange.tabId = args.tabId;
}
log.info(`Deleting content from index ${args.startIndex} to ${args.endIndex}`);
await GDocsHelpers.executeBatchUpdate(docs, args.documentId, [
{
deleteContentRange: { range: deleteRange },
},
]);
log.info(`Delete complete.`);
// 2. Insert markdown at the start position (which is now where the deleted content was)
log.info(`Inserting markdown at index ${args.startIndex}`);
const result = await insertMarkdown(docs, args.documentId, args.markdown, {
startIndex: args.startIndex,
tabId: args.tabId,
});
const debugSummary = formatInsertResult(result);
log.info(debugSummary);
return `Successfully replaced range ${args.startIndex}-${args.endIndex} with ${args.markdown.length} characters of markdown.\n\n${debugSummary}`;
} catch (error: any) {
log.error(`Error replacing range with markdown: ${error.message}`);
if (error instanceof UserError || error instanceof MarkdownConversionError) {
throw error;
}
throw new UserError(
`Failed to replace range with markdown: ${error.message || 'Unknown error'}`
);
}
},
});
}