delete_comment
Remove a comment and all its threaded replies from a DOCX document. Specify the file path and comment ID to delete comments while preserving document formatting.
Instructions
Delete a comment and all its threaded replies from the document. Cascade-deletes all descendants.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| file_path | Yes | Path to the DOCX file. | |
| comment_id | Yes | Comment ID to delete. |
Implementation Reference
- MCP tool handler that validates input, resolves the document session, calls the core deleteComment primitive, and returns the appropriate tool response.
export async function deleteComment( manager: SessionManager, params: { file_path?: string; comment_id?: number; }, ): Promise<ToolResponse> { const resolved = await resolveSessionForTool(manager, params, { toolName: 'delete_comment' }); if (!resolved.ok) return resolved.response; const { session, metadata } = resolved; if (params.comment_id == null) { return err('MISSING_PARAMETER', 'comment_id is required.', 'Provide the comment ID to delete.'); } try { await session.doc.deleteComment({ commentId: params.comment_id }); manager.markEdited(session); return ok(mergeSessionResolutionMetadata({ comment_id: params.comment_id, file_path: manager.normalizePath(session.originalPath), }, metadata)); } catch (e: unknown) { const msg = errorMessage(e); if (msg.includes('not found')) { return err('COMMENT_NOT_FOUND', msg, 'Use get_comments to list available comments.'); } return err('COMMENT_ERROR', msg); } } - Core implementation of comment deletion. It manipulates the document XML structure, including removing comment elements, extended metadata, and associated markers/references in document.xml.
export async function deleteComment( documentXml: Document, zip: DocxZip, params: { commentId: number }, ): Promise<void> { const { commentId } = params; const commentsText = await zip.readTextOrNull('word/comments.xml'); if (!commentsText) throw new Error(`Comment ID ${commentId} not found`); const commentsDoc = parseXml(commentsText); // Find the target comment element and its paraId const targetEl = findCommentElementById(commentsDoc, commentId); if (!targetEl) throw new Error(`Comment ID ${commentId} not found`); const targetParaId = getCommentElParaId(targetEl); // Collect all IDs to delete: the target + all transitive descendants const idsToDelete = new Set<number>([commentId]); const paraIdsToDelete = new Set<string>(); if (targetParaId) paraIdsToDelete.add(targetParaId); // Build paraId→commentId and paraId→commentEl maps for all comments const paraIdToId = new Map<string, number>(); const allCommentEls = commentsDoc.getElementsByTagNameNS(OOXML.W_NS, W.comment); for (let i = 0; i < allCommentEls.length; i++) { const el = allCommentEls.item(i) as Element; const idStr = el.getAttributeNS(OOXML.W_NS, 'id') ?? el.getAttribute('w:id'); const id = idStr ? parseInt(idStr, 10) : -1; if (id < 0) continue; const pid = getCommentElParaId(el); if (pid) paraIdToId.set(pid, id); } // Read commentsExtended.xml to find descendants via paraIdParent graph const extText = await zip.readTextOrNull('word/commentsExtended.xml'); if (extText) { const extDoc = parseXml(extText); const exEls = extDoc.getElementsByTagNameNS(OOXML.W15_NS, 'commentEx'); // Build parent→children map const childrenOf = new Map<string, string[]>(); for (let i = 0; i < exEls.length; i++) { const ex = exEls.item(i) as Element; const childPid = ex.getAttributeNS(OOXML.W15_NS, 'paraId') ?? ex.getAttribute('w15:paraId'); const parentPid = ex.getAttributeNS(OOXML.W15_NS, 'paraIdParent') ?? ex.getAttribute('w15:paraIdParent'); if (childPid && parentPid) { const arr = childrenOf.get(parentPid); if (arr) arr.push(childPid); else childrenOf.set(parentPid, [childPid]); } } // BFS from target paraId to collect all descendant paraIds const queue = targetParaId ? [targetParaId] : []; while (queue.length > 0) { const pid = queue.shift()!; const children = childrenOf.get(pid); if (!children) continue; for (const childPid of children) { if (!paraIdsToDelete.has(childPid)) { paraIdsToDelete.add(childPid); const childId = paraIdToId.get(childPid); if (childId != null) idsToDelete.add(childId); queue.push(childPid); } } } } // 1. Remove comment elements from comments.xml const elsToRemove: Element[] = []; for (let i = 0; i < allCommentEls.length; i++) { const el = allCommentEls.item(i) as Element; const idStr = el.getAttributeNS(OOXML.W_NS, 'id') ?? el.getAttribute('w:id'); const id = idStr ? parseInt(idStr, 10) : -1; if (idsToDelete.has(id)) elsToRemove.push(el); } for (const el of elsToRemove) { el.parentNode?.removeChild(el); } zip.writeText('word/comments.xml', serializeXml(commentsDoc)); // 2. Remove commentEx entries from commentsExtended.xml (if present) if (extText) { const extDoc = parseXml(extText); const exEls = extDoc.getElementsByTagNameNS(OOXML.W15_NS, 'commentEx'); const exToRemove: Element[] = []; for (let i = 0; i < exEls.length; i++) { const ex = exEls.item(i) as Element; const pid = ex.getAttributeNS(OOXML.W15_NS, 'paraId') ?? ex.getAttribute('w15:paraId'); if (pid && paraIdsToDelete.has(pid)) exToRemove.push(ex); } for (const ex of exToRemove) { ex.parentNode?.removeChild(ex); } zip.writeText('word/commentsExtended.xml', serializeXml(extDoc)); } // 3. Remove range markers and commentReference from document.xml (for root comments) for (const cid of idsToDelete) { removeCommentMarkersFromDocument(documentXml, cid); } }