Skip to main content
Glama

Office Word MCP Server

main.py24.8 kB
""" Main entry point for the Word Document MCP Server. Acts as the central controller for the MCP server that handles Word document operations. Supports multiple transports: stdio, sse, and streamable-http using standalone FastMCP. """ import os import sys from dotenv import load_dotenv # Load environment variables from .env file print("Loading configuration from .env file...") load_dotenv() # Set required environment variable for FastMCP 2.8.1+ os.environ.setdefault('FASTMCP_LOG_LEVEL', 'INFO') from fastmcp import FastMCP from word_document_server.tools import ( document_tools, content_tools, format_tools, protection_tools, footnote_tools, extended_document_tools, comment_tools ) from word_document_server.tools.content_tools import replace_paragraph_block_below_header_tool from word_document_server.tools.content_tools import replace_block_between_manual_anchors_tool def get_transport_config(): """ Get transport configuration from environment variables. Returns: dict: Transport configuration with type, host, port, and other settings """ # Default configuration config = { 'transport': 'stdio', # Default to stdio for backward compatibility 'host': '0.0.0.0', 'port': 8000, 'path': '/mcp', 'sse_path': '/sse' } # Override with environment variables if provided transport = os.getenv('MCP_TRANSPORT', 'stdio').lower() print(f"Transport: {transport}") # Validate transport type valid_transports = ['stdio', 'streamable-http', 'sse'] if transport not in valid_transports: print(f"Warning: Invalid transport '{transport}'. Falling back to 'stdio'.") transport = 'stdio' config['transport'] = transport config['host'] = os.getenv('MCP_HOST', config['host']) # Use PORT from Render if available, otherwise fall back to MCP_PORT or default config['port'] = int(os.getenv('PORT', os.getenv('MCP_PORT', config['port']))) config['path'] = os.getenv('MCP_PATH', config['path']) config['sse_path'] = os.getenv('MCP_SSE_PATH', config['sse_path']) return config def setup_logging(debug_mode): """ Setup logging based on debug mode. Args: debug_mode (bool): Whether to enable debug logging """ import logging if debug_mode: logging.basicConfig( level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) print("Debug logging enabled") else: logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s' ) # Initialize FastMCP server mcp = FastMCP("Word Document Server") def register_tools(): """Register all tools with the MCP server using FastMCP decorators.""" # Document tools (create, copy, info, etc.) @mcp.tool() def create_document(filename: str, title: str = None, author: str = None): """Create a new Word document with optional metadata.""" return document_tools.create_document(filename, title, author) @mcp.tool() def copy_document(source_filename: str, destination_filename: str = None): """Create a copy of a Word document.""" return document_tools.copy_document(source_filename, destination_filename) @mcp.tool() def get_document_info(filename: str): """Get information about a Word document.""" return document_tools.get_document_info(filename) @mcp.tool() def get_document_text(filename: str): """Extract all text from a Word document.""" return document_tools.get_document_text(filename) @mcp.tool() def get_document_outline(filename: str): """Get the structure of a Word document.""" return document_tools.get_document_outline(filename) @mcp.tool() def list_available_documents(directory: str = "."): """List all .docx files in the specified directory.""" return document_tools.list_available_documents(directory) @mcp.tool() def get_document_xml(filename: str): """Get the raw XML structure of a Word document.""" return document_tools.get_document_xml_tool(filename) @mcp.tool() def insert_header_near_text(filename: str, target_text: str = None, header_title: str = None, position: str = 'after', header_style: str = 'Heading 1', target_paragraph_index: int = None): """Insert a header (with specified style) before or after the target paragraph. Specify by text or paragraph index. Args: filename (str), target_text (str, optional), header_title (str), position ('before' or 'after'), header_style (str, default 'Heading 1'), target_paragraph_index (int, optional).""" return content_tools.insert_header_near_text_tool(filename, target_text, header_title, position, header_style, target_paragraph_index) @mcp.tool() def insert_line_or_paragraph_near_text(filename: str, target_text: str = None, line_text: str = None, position: str = 'after', line_style: str = None, target_paragraph_index: int = None): """ Insert a new line or paragraph (with specified or matched style) before or after the target paragraph. Specify by text or paragraph index. Args: filename (str), target_text (str, optional), line_text (str), position ('before' or 'after'), line_style (str, optional), target_paragraph_index (int, optional). """ return content_tools.insert_line_or_paragraph_near_text_tool(filename, target_text, line_text, position, line_style, target_paragraph_index) @mcp.tool() def insert_numbered_list_near_text(filename: str, target_text: str = None, list_items: list = None, position: str = 'after', target_paragraph_index: int = None, bullet_type: str = 'bullet'): """Insert a bulleted or numbered list before or after the target paragraph. Specify by text or paragraph index. Args: filename (str), target_text (str, optional), list_items (list of str), position ('before' or 'after'), target_paragraph_index (int, optional), bullet_type ('bullet' for bullets or 'number' for numbered lists, default: 'bullet').""" return content_tools.insert_numbered_list_near_text_tool(filename, target_text, list_items, position, target_paragraph_index, bullet_type) # Content tools (paragraphs, headings, tables, etc.) @mcp.tool() def add_paragraph(filename: str, text: str, style: str = None, font_name: str = None, font_size: int = None, bold: bool = None, italic: bool = None, color: str = None): """Add a paragraph to a Word document with optional formatting. Args: filename: Path to Word document text: Paragraph text content style: Optional paragraph style name font_name: Font family (e.g., 'Helvetica', 'Times New Roman') font_size: Font size in points (e.g., 14, 36) bold: Make text bold italic: Make text italic color: Text color as hex RGB (e.g., '000000') """ return content_tools.add_paragraph(filename, text, style, font_name, font_size, bold, italic, color) @mcp.tool() def add_heading(filename: str, text: str, level: int = 1, font_name: str = None, font_size: int = None, bold: bool = None, italic: bool = None, border_bottom: bool = False): """Add a heading to a Word document with optional formatting. Args: filename: Path to Word document text: Heading text level: Heading level (1-9) font_name: Font family (e.g., 'Helvetica') font_size: Font size in points (e.g., 14) bold: Make heading bold italic: Make heading italic border_bottom: Add bottom border (for section headers) """ return content_tools.add_heading(filename, text, level, font_name, font_size, bold, italic, border_bottom) @mcp.tool() def add_picture(filename: str, image_path: str, width: float = None): """Add an image to a Word document.""" return content_tools.add_picture(filename, image_path, width) @mcp.tool() def add_table(filename: str, rows: int, cols: int, data: list = None): """Add a table to a Word document.""" return content_tools.add_table(filename, rows, cols, data) @mcp.tool() def add_page_break(filename: str): """Add a page break to the document.""" return content_tools.add_page_break(filename) @mcp.tool() def delete_paragraph(filename: str, paragraph_index: int): """Delete a paragraph from a document.""" return content_tools.delete_paragraph(filename, paragraph_index) @mcp.tool() def search_and_replace(filename: str, find_text: str, replace_text: str): """Search for text and replace all occurrences.""" return content_tools.search_and_replace(filename, find_text, replace_text) # Format tools (styling, text formatting, etc.) @mcp.tool() def create_custom_style(filename: str, style_name: str, bold: bool = None, italic: bool = None, font_size: int = None, font_name: str = None, color: str = None, base_style: str = None): """Create a custom style in the document.""" return format_tools.create_custom_style( filename, style_name, bold, italic, font_size, font_name, color, base_style ) @mcp.tool() def format_text(filename: str, paragraph_index: int, start_pos: int, end_pos: int, bold: bool = None, italic: bool = None, underline: bool = None, color: str = None, font_size: int = None, font_name: str = None): """Format a specific range of text within a paragraph.""" return format_tools.format_text( filename, paragraph_index, start_pos, end_pos, bold, italic, underline, color, font_size, font_name ) @mcp.tool() def format_table(filename: str, table_index: int, has_header_row: bool = None, border_style: str = None, shading: list = None): """Format a table with borders, shading, and structure.""" return format_tools.format_table(filename, table_index, has_header_row, border_style, shading) # New table cell shading tools @mcp.tool() def set_table_cell_shading(filename: str, table_index: int, row_index: int, col_index: int, fill_color: str, pattern: str = "clear"): """Apply shading/filling to a specific table cell.""" return format_tools.set_table_cell_shading(filename, table_index, row_index, col_index, fill_color, pattern) @mcp.tool() def apply_table_alternating_rows(filename: str, table_index: int, color1: str = "FFFFFF", color2: str = "F2F2F2"): """Apply alternating row colors to a table for better readability.""" return format_tools.apply_table_alternating_rows(filename, table_index, color1, color2) @mcp.tool() def highlight_table_header(filename: str, table_index: int, header_color: str = "4472C4", text_color: str = "FFFFFF"): """Apply special highlighting to table header row.""" return format_tools.highlight_table_header(filename, table_index, header_color, text_color) # Cell merging tools @mcp.tool() def merge_table_cells(filename: str, table_index: int, start_row: int, start_col: int, end_row: int, end_col: int): """Merge cells in a rectangular area of a table.""" return format_tools.merge_table_cells(filename, table_index, start_row, start_col, end_row, end_col) @mcp.tool() def merge_table_cells_horizontal(filename: str, table_index: int, row_index: int, start_col: int, end_col: int): """Merge cells horizontally in a single row.""" return format_tools.merge_table_cells_horizontal(filename, table_index, row_index, start_col, end_col) @mcp.tool() def merge_table_cells_vertical(filename: str, table_index: int, col_index: int, start_row: int, end_row: int): """Merge cells vertically in a single column.""" return format_tools.merge_table_cells_vertical(filename, table_index, col_index, start_row, end_row) # Cell alignment tools @mcp.tool() def set_table_cell_alignment(filename: str, table_index: int, row_index: int, col_index: int, horizontal: str = "left", vertical: str = "top"): """Set text alignment for a specific table cell.""" return format_tools.set_table_cell_alignment(filename, table_index, row_index, col_index, horizontal, vertical) @mcp.tool() def set_table_alignment_all(filename: str, table_index: int, horizontal: str = "left", vertical: str = "top"): """Set text alignment for all cells in a table.""" return format_tools.set_table_alignment_all(filename, table_index, horizontal, vertical) # Protection tools @mcp.tool() def protect_document(filename: str, password: str): """Add password protection to a Word document.""" return protection_tools.protect_document(filename, password) @mcp.tool() def unprotect_document(filename: str, password: str): """Remove password protection from a Word document.""" return protection_tools.unprotect_document(filename, password) # Footnote tools @mcp.tool() def add_footnote_to_document(filename: str, paragraph_index: int, footnote_text: str): """Add a footnote to a specific paragraph in a Word document.""" return footnote_tools.add_footnote_to_document(filename, paragraph_index, footnote_text) @mcp.tool() def add_footnote_after_text(filename: str, search_text: str, footnote_text: str, output_filename: str = None): """Add a footnote after specific text with proper superscript formatting. This enhanced function ensures footnotes display correctly as superscript.""" return footnote_tools.add_footnote_after_text(filename, search_text, footnote_text, output_filename) @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) @mcp.tool() def add_footnote_enhanced(filename: str, paragraph_index: int, footnote_text: str, output_filename: str = None): """Enhanced footnote addition with guaranteed superscript formatting. Adds footnote at the end of a specific paragraph with proper style handling.""" return footnote_tools.add_footnote_enhanced(filename, paragraph_index, footnote_text, output_filename) @mcp.tool() def add_endnote_to_document(filename: str, paragraph_index: int, endnote_text: str): """Add an endnote to a specific paragraph in a Word document.""" return footnote_tools.add_endnote_to_document(filename, paragraph_index, endnote_text) @mcp.tool() def customize_footnote_style(filename: str, numbering_format: str = "1, 2, 3", start_number: int = 1, font_name: str = None, font_size: int = None): """Customize footnote numbering and formatting in a Word document.""" return footnote_tools.customize_footnote_style( filename, numbering_format, start_number, font_name, font_size ) @mcp.tool() def delete_footnote_from_document(filename: str, footnote_id: int = None, search_text: str = None, output_filename: str = None): """Delete a footnote from a Word document. Identify the footnote either by ID (1, 2, 3, etc.) or by searching for text near it.""" return footnote_tools.delete_footnote_from_document( filename, footnote_id, search_text, output_filename ) # Robust footnote tools - Production-ready with comprehensive validation @mcp.tool() def add_footnote_robust(filename: str, search_text: str = None, paragraph_index: int = None, footnote_text: str = "", validate_location: bool = True, auto_repair: bool = False): """Add footnote with robust validation and Word compliance. This is the production-ready version with comprehensive error handling.""" return footnote_tools.add_footnote_robust_tool( filename, search_text, paragraph_index, footnote_text, validate_location, auto_repair ) @mcp.tool() def validate_document_footnotes(filename: str): """Validate all footnotes in document for coherence and compliance. Returns detailed report on ID conflicts, orphaned content, missing styles, etc.""" return footnote_tools.validate_footnotes_tool(filename) @mcp.tool() def delete_footnote_robust(filename: str, footnote_id: int = None, search_text: str = None, clean_orphans: bool = True): """Delete footnote with comprehensive cleanup and orphan removal. Ensures complete removal from document.xml, footnotes.xml, and relationships.""" return footnote_tools.delete_footnote_robust_tool( filename, footnote_id, search_text, clean_orphans ) # Extended document tools @mcp.tool() def get_paragraph_text_from_document(filename: str, paragraph_index: int): """Get text from a specific paragraph in a Word document.""" return extended_document_tools.get_paragraph_text_from_document(filename, paragraph_index) @mcp.tool() def find_text_in_document(filename: str, text_to_find: str, match_case: bool = True, whole_word: bool = False): """Find occurrences of specific text in a Word document.""" return extended_document_tools.find_text_in_document( filename, text_to_find, match_case, whole_word ) @mcp.tool() def convert_to_pdf(filename: str, output_filename: str = None): """Convert a Word document to PDF format.""" return extended_document_tools.convert_to_pdf(filename, output_filename) @mcp.tool() def replace_paragraph_block_below_header(filename: str, header_text: str, new_paragraphs: list, detect_block_end_fn=None): """Reemplaza el bloque de párrafos debajo de un encabezado, evitando modificar TOC.""" return replace_paragraph_block_below_header_tool(filename, header_text, new_paragraphs, detect_block_end_fn) @mcp.tool() def replace_block_between_manual_anchors(filename: str, start_anchor_text: str, new_paragraphs: list, end_anchor_text: str = None, match_fn=None, new_paragraph_style: str = None): """Replace all content between start_anchor_text and end_anchor_text (or next logical header if not provided).""" return replace_block_between_manual_anchors_tool(filename, start_anchor_text, new_paragraphs, end_anchor_text, match_fn, new_paragraph_style) # Comment tools @mcp.tool() def get_all_comments(filename: str): """Extract all comments from a Word document.""" return comment_tools.get_all_comments(filename) @mcp.tool() def get_comments_by_author(filename: str, author: str): """Extract comments from a specific author in a Word document.""" return comment_tools.get_comments_by_author(filename, author) @mcp.tool() def get_comments_for_paragraph(filename: str, paragraph_index: int): """Extract comments for a specific paragraph in a Word document.""" return comment_tools.get_comments_for_paragraph(filename, paragraph_index) # New table column width tools @mcp.tool() def set_table_column_width(filename: str, table_index: int, col_index: int, width: float, width_type: str = "points"): """Set the width of a specific table column.""" return format_tools.set_table_column_width(filename, table_index, col_index, width, width_type) @mcp.tool() def set_table_column_widths(filename: str, table_index: int, widths: list, width_type: str = "points"): """Set the widths of multiple table columns.""" return format_tools.set_table_column_widths(filename, table_index, widths, width_type) @mcp.tool() def set_table_width(filename: str, table_index: int, width: float, width_type: str = "points"): """Set the overall width of a table.""" return format_tools.set_table_width(filename, table_index, width, width_type) @mcp.tool() def auto_fit_table_columns(filename: str, table_index: int): """Set table columns to auto-fit based on content.""" return format_tools.auto_fit_table_columns(filename, table_index) # New table cell text formatting and padding tools @mcp.tool() def format_table_cell_text(filename: str, table_index: int, row_index: int, col_index: int, text_content: str = None, bold: bool = None, italic: bool = None, underline: bool = None, color: str = None, font_size: int = None, font_name: str = None): """Format text within a specific table cell.""" return format_tools.format_table_cell_text(filename, table_index, row_index, col_index, text_content, bold, italic, underline, color, font_size, font_name) @mcp.tool() def set_table_cell_padding(filename: str, table_index: int, row_index: int, col_index: int, top: float = None, bottom: float = None, left: float = None, right: float = None, unit: str = "points"): """Set padding/margins for a specific table cell.""" return format_tools.set_table_cell_padding(filename, table_index, row_index, col_index, top, bottom, left, right, unit) def run_server(): """Run the Word Document MCP Server with configurable transport.""" # Get transport configuration config = get_transport_config() # Setup logging # setup_logging(config['debug']) # Register all tools register_tools() # Print startup information transport_type = config['transport'] print(f"Starting Word Document MCP Server with {transport_type} transport...") # if config['debug']: # print(f"Configuration: {config}") try: if transport_type == 'stdio': # Run with stdio transport (default, backward compatible) print("Server running on stdio transport") mcp.run(transport='stdio') elif transport_type == 'streamable-http': # Run with streamable HTTP transport print(f"Server running on streamable-http transport at http://{config['host']}:{config['port']}{config['path']}") mcp.run( transport='streamable-http', host=config['host'], port=config['port'], path=config['path'] ) elif transport_type == 'sse': # Run with SSE transport print(f"Server running on SSE transport at http://{config['host']}:{config['port']}{config['sse_path']}") mcp.run( transport='sse', host=config['host'], port=config['port'], path=config['sse_path'] ) except KeyboardInterrupt: print("\nShutting down server...") except Exception as e: print(f"Error starting server: {e}") if config['debug']: import traceback traceback.print_exc() sys.exit(1) return mcp def main(): """Main entry point for the server.""" run_server() if __name__ == "__main__": main()

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