add_footnote
Add explanatory footnotes to DOCX documents by anchoring them to specific paragraphs. Insert references after designated text or at paragraph ends to provide citations and additional context while preserving document formatting.
Instructions
Add a footnote anchored to a paragraph. Optionally position the reference after specific text using after_text. Note: [^N] markers in read_file output are display-only and not part of the editable text used by replace_text.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| file_path | Yes | Path to the DOCX file. | |
| target_paragraph_id | Yes | Paragraph ID to anchor the footnote to. | |
| after_text | No | Text after which to insert the footnote reference. If omitted, appends at end of paragraph. | |
| text | Yes | Footnote body text. |
Implementation Reference
- The MCP tool handler for 'add_footnote', which validates input, resolves the document session, and calls the low-level docx-core addFootnote implementation.
export async function addFootnote( manager: SessionManager, params: { file_path?: string; target_paragraph_id?: string; after_text?: string; text?: string; }, ): Promise<ToolResponse> { const resolved = await resolveSessionForTool(manager, params, { toolName: 'add_footnote' }); if (!resolved.ok) return resolved.response; const { session, metadata } = resolved; if (!params.target_paragraph_id) { return err('MISSING_PARAMETER', 'target_paragraph_id is required.', 'Provide the _bk_* ID of the paragraph to anchor the footnote to.'); } if (!params.text) { return err('MISSING_PARAMETER', 'text is required.', 'Provide the footnote body text.'); } const pid = params.target_paragraph_id; const pEl = session.doc.getParagraphElementById(pid); if (!pEl) { return err('ANCHOR_NOT_FOUND', `Paragraph ID ${pid} not found in document`, 'Use grep or read_file to find valid paragraph IDs.'); } try { const result = await session.doc.addFootnote({ paragraphId: pid, afterText: params.after_text, text: params.text, }); manager.markEdited(session); return ok(mergeSessionResolutionMetadata({ note_id: result.noteId, target_paragraph_id: pid, after_text: params.after_text ?? null, file_path: manager.normalizePath(session.originalPath), }, metadata)); } catch (e: unknown) { const msg = errorMessage(e); if (msg.includes('not found in paragraph')) { return err('TEXT_NOT_FOUND', msg, 'Verify after_text is present in the target paragraph.'); } if (msg.includes('found') && msg.includes('times')) { return err('MULTIPLE_MATCHES', msg, 'Provide more specific after_text for a unique match.'); } return err('FOOTNOTE_ERROR', msg); } } - The core implementation of adding a footnote to a DOCX document, handling XML structure for footnotes.xml and updating document references.
export async function addFootnote( documentXml: Document, zip: DocxZip, params: AddFootnoteParams, ): Promise<AddFootnoteResult> { const { paragraphEl, afterText, text } = params; // Load or bootstrap footnotes.xml const footnotesXml = await zip.readText('word/footnotes.xml'); const footnotesDoc = parseXml(footnotesXml); // Allocate next ID const noteId = allocateNextFootnoteId(footnotesDoc); // Insert footnoteReference run in document body insertFootnoteReference(documentXml, paragraphEl, noteId, afterText); // Add footnote body to footnotes.xml addFootnoteElement(footnotesDoc, noteId, text); zip.writeText('word/footnotes.xml', serializeXml(footnotesDoc)); return { noteId }; }