Skip to main content
Glama
GongRzhe

Office Word MCP Server

add_footnote_before_text

Add footnotes before specific text in Word documents with proper superscript formatting to ensure correct display and citation placement.

Instructions

Add a footnote before specific text with proper superscript formatting. This enhanced function ensures footnotes display correctly as superscript.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
filenameYes
search_textYes
footnote_textYes
output_filenameNo

Implementation Reference

  • MCP tool registration for 'add_footnote_before_text' which delegates execution to the handler in footnote_tools.py
    @mcp.tool()
    def add_footnote_before_text(filename: str, search_text: str, footnote_text: str, 
                                output_filename: str = None):
        """Add a footnote before specific text with proper superscript formatting.
        This enhanced function ensures footnotes display correctly as superscript."""
        return footnote_tools.add_footnote_before_text(filename, search_text, footnote_text, output_filename)
  • Primary handler logic: validates inputs, checks file accessibility, and invokes core robust footnote addition with position='before'
    async def add_footnote_before_text(filename: str, search_text: str, footnote_text: str, 
                                      output_filename: Optional[str] = None) -> str:
        """Add a footnote before specific text in a Word document with proper formatting.
        
        This enhanced function ensures proper superscript formatting by managing styles at the XML level.
        
        Args:
            filename: Path to the Word document
            search_text: Text to search for (footnote will be added before this text)
            footnote_text: Content of the footnote
            output_filename: Optional output filename (if None, modifies in place)
        """
        filename = ensure_docx_extension(filename)
        
        if not os.path.exists(filename):
            return f"Document {filename} does not exist"
        
        # Check if file is writeable
        is_writeable, error_message = check_file_writeable(filename)
        if not is_writeable:
            return f"Cannot modify document: {error_message}. Consider creating a copy first."
        
        try:
            # Use robust implementation
            success, message, details = add_footnote_robust(
                filename=filename,
                search_text=search_text,
                footnote_text=footnote_text,
                output_filename=output_filename,
                position="before",
                validate_location=True
            )
            return message
        except Exception as e:
            return f"Failed to add footnote: {str(e)}"
  • Core helper function performing low-level DOCX XML editing to insert proper Word footnote reference and content, supporting 'before'/'after' positions via direct elementtree manipulation and zip handling
    def add_footnote_robust(
        filename: str,
        search_text: Optional[str] = None,
        paragraph_index: Optional[int] = None,
        footnote_text: str = "",
        output_filename: Optional[str] = None,
        position: str = "after",
        validate_location: bool = True,
        auto_repair: bool = False
    ) -> Tuple[bool, str, Optional[Dict[str, Any]]]:
        """
        Add a footnote with robust validation and error handling.
        
        This is the main production-ready function with all fixes applied.
        """
        
        # Validate inputs
        if not search_text and paragraph_index is None:
            return False, "Must provide either search_text or paragraph_index", None
        
        if search_text and paragraph_index is not None:
            return False, "Cannot provide both search_text and paragraph_index", None
        
        if not os.path.exists(filename):
            return False, f"File not found: {filename}", None
        
        # Set working file
        working_file = output_filename if output_filename else filename
        if output_filename and filename != output_filename:
            import shutil
            shutil.copy2(filename, output_filename)
        
        try:
            # Read document parts
            doc_parts = {}
            with zipfile.ZipFile(filename, 'r') as zin:
                doc_parts['document'] = zin.read('word/document.xml')
                doc_parts['content_types'] = zin.read('[Content_Types].xml')
                doc_parts['document_rels'] = zin.read('word/_rels/document.xml.rels')
                
                # Read or create footnotes.xml
                if 'word/footnotes.xml' in zin.namelist():
                    doc_parts['footnotes'] = zin.read('word/footnotes.xml')
                else:
                    doc_parts['footnotes'] = _create_minimal_footnotes_xml()
                
                # Read styles
                if 'word/styles.xml' in zin.namelist():
                    doc_parts['styles'] = zin.read('word/styles.xml')
                else:
                    # Create minimal styles
                    doc_parts['styles'] = b'<?xml version="1.0" encoding="UTF-8" standalone="yes"?><w:styles xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"/>'
            
            # Parse XML documents
            doc_root = etree.fromstring(doc_parts['document'])
            footnotes_root = etree.fromstring(doc_parts['footnotes'])
            styles_root = etree.fromstring(doc_parts['styles'])
            
            # Find target location
            nsmap = {'w': W_NS}
            
            if search_text:
                # Search for text in paragraphs
                found = False
                for para in doc_root.xpath('//w:p', namespaces=nsmap):
                    para_text = ''.join(para.xpath('.//w:t/text()', namespaces=nsmap))
                    if search_text in para_text:
                        target_para = para
                        found = True
                        break
                
                if not found:
                    return False, f"Text '{search_text}' not found in document", None
            else:
                # Use paragraph index
                paragraphs = doc_root.xpath('//w:p', namespaces=nsmap)
                if paragraph_index >= len(paragraphs):
                    return False, f"Paragraph index {paragraph_index} out of range", None
                target_para = paragraphs[paragraph_index]
            
            # Validate location if requested
            if validate_location:
                # Check if paragraph is in header/footer
                parent = target_para.getparent()
                while parent is not None:
                    if parent.tag in [f'{{{W_NS}}}hdr', f'{{{W_NS}}}ftr']:
                        return False, "Cannot add footnote in header/footer", None
                    parent = parent.getparent()
            
            # Get safe footnote ID
            footnote_id = _get_safe_footnote_id(footnotes_root)
            
            # Add footnote reference to document
            if position == "after":
                # Find last run in paragraph or create one
                runs = target_para.xpath('.//w:r', namespaces=nsmap)
                if runs:
                    last_run = runs[-1]
                    # Insert after last run
                    insert_pos = target_para.index(last_run) + 1
                else:
                    insert_pos = len(target_para)
            else:  # before
                # Find first run with text
                runs = target_para.xpath('.//w:r[w:t]', namespaces=nsmap)
                if runs:
                    first_run = runs[0]
                    insert_pos = target_para.index(first_run)
                else:
                    insert_pos = 0
            
            # Create footnote reference run
            ref_run = etree.Element(f'{{{W_NS}}}r')
            
            # Add run properties with superscript
            rPr = etree.SubElement(ref_run, f'{{{W_NS}}}rPr')
            rStyle = etree.SubElement(rPr, f'{{{W_NS}}}rStyle')
            rStyle.set(f'{{{W_NS}}}val', 'FootnoteReference')
            
            # Add footnote reference
            fn_ref = etree.SubElement(ref_run, f'{{{W_NS}}}footnoteReference')
            fn_ref.set(f'{{{W_NS}}}id', str(footnote_id))
            
            # Insert the reference run
            target_para.insert(insert_pos, ref_run)
            
            # Add footnote content
            new_footnote = etree.Element(f'{{{W_NS}}}footnote',
                attrib={f'{{{W_NS}}}id': str(footnote_id)}
            )
            
            # Add paragraph to footnote
            fn_para = etree.SubElement(new_footnote, f'{{{W_NS}}}p')
            
            # Add paragraph properties
            pPr = etree.SubElement(fn_para, f'{{{W_NS}}}pPr')
            pStyle = etree.SubElement(pPr, f'{{{W_NS}}}pStyle')
            pStyle.set(f'{{{W_NS}}}val', 'FootnoteText')
            
            # Add the footnote reference marker
            marker_run = etree.SubElement(fn_para, f'{{{W_NS}}}r')
            marker_rPr = etree.SubElement(marker_run, f'{{{W_NS}}}rPr')
            marker_rStyle = etree.SubElement(marker_rPr, f'{{{W_NS}}}rStyle')
            marker_rStyle.set(f'{{{W_NS}}}val', 'FootnoteReference')
            marker_ref = etree.SubElement(marker_run, f'{{{W_NS}}}footnoteRef')
            
            # Add space after marker
            space_run = etree.SubElement(fn_para, f'{{{W_NS}}}r')
            space_text = etree.SubElement(space_run, f'{{{W_NS}}}t')
            space_text.set(f'{{{XML_NS}}}space', 'preserve')
            space_text.text = ' '
            
            # Add footnote text
            text_run = etree.SubElement(fn_para, f'{{{W_NS}}}r')
            text_elem = etree.SubElement(text_run, f'{{{W_NS}}}t')
            text_elem.text = footnote_text
            
            # Append footnote to footnotes.xml
            footnotes_root.append(new_footnote)
            
            # Ensure styles exist
            _ensure_footnote_styles(styles_root)
            
            # Ensure coherence
            content_types_xml = _ensure_content_types(doc_parts['content_types'])
            document_rels_xml = _ensure_document_rels(doc_parts['document_rels'])
            
            # Write modified document
            temp_file = working_file + '.tmp'
            with zipfile.ZipFile(temp_file, 'w', zipfile.ZIP_DEFLATED) as zout:
                with zipfile.ZipFile(filename, 'r') as zin:
                    # Copy unchanged files
                    for item in zin.infolist():
                        if item.filename not in [
                            'word/document.xml', 'word/footnotes.xml', 'word/styles.xml',
                            '[Content_Types].xml', 'word/_rels/document.xml.rels'
                        ]:
                            zout.writestr(item, zin.read(item.filename))
                
                # Write modified files
                zout.writestr('word/document.xml',
                    etree.tostring(doc_root, encoding='UTF-8', xml_declaration=True, standalone="yes"))
                zout.writestr('word/footnotes.xml',
                    etree.tostring(footnotes_root, encoding='UTF-8', xml_declaration=True, standalone="yes"))
                zout.writestr('word/styles.xml',
                    etree.tostring(styles_root, encoding='UTF-8', xml_declaration=True, standalone="yes"))
                zout.writestr('[Content_Types].xml', content_types_xml)
                zout.writestr('word/_rels/document.xml.rels', document_rels_xml)
            
            # Replace original with temp file
            os.replace(temp_file, working_file)
            
            details = {
                'footnote_id': footnote_id,
                'location': 'search_text' if search_text else 'paragraph_index',
                'styles_created': ['FootnoteReference', 'FootnoteText'],
                'coherence_verified': True
            }
            
            return True, f"Successfully added footnote (ID: {footnote_id}) to {working_file}", details
            
        except Exception as e:
            # Clean up temp file if exists
            temp_file = working_file + '.tmp'
            if os.path.exists(temp_file):
                os.remove(temp_file)
            return False, f"Error adding footnote: {str(e)}", None

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/GongRzhe/Office-Word-MCP-Server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server