diff --git a/src/server.py b/src/server.py index 0c36aba..d7c51cd 100644 --- a/src/server.py +++ b/src/server.py @@ -3,7 +3,6 @@ from __future__ import annotations import json import logging import os -import sys from typing import Any from mcp.server.fastmcp import FastMCP @@ -95,579 +94,469 @@ def make_server( templates_dir=TEMPLATES_DIR, ) - def wrap(fn, name: str): - def wrapper(**kwargs): - if not is_command_allowed(name, security_config): - raise ValueError(f"Command '{name}' not allowed by security policy") - return fn(**kwargs) - wrapper.__name__ = fn.__name__ - return wrapper + def require_allowed(tool_name: str): + def decorator(fn): + def wrapper(*args, **kwargs): + if not is_command_allowed(tool_name, security_config): + raise ValueError(f"Command '{tool_name}' not allowed by security policy") + return fn(*args, **kwargs) + wrapper.__name__ = fn.__name__ + wrapper.__doc__ = fn.__doc__ + return wrapper + return decorator - # Core document operations - mcp.tool()( - wrap(lambda: provider.create_document(), "create_document"), - name="create_document", - description="Create a new empty DOCX document", - ) + @mcp.tool + @require_allowed("create_document") + def create_document(): + """Create a new empty DOCX document""" + return provider.create_document() - mcp.tool()( - wrap(lambda path: provider.open_document(path), "open_document"), - name="open_document", - description="Open an existing DOCX document", - ) + @mcp.tool + @require_allowed("open_document") + def open_document(path: str): + """Open an existing DOCX document""" + return provider.open_document(path) - mcp.tool()( - wrap( - lambda document_id, text, style=None, return_content=False: provider.add_paragraph( - document_id, text, style or {}, return_content=return_content - ), - "add_paragraph", - ), - name="add_paragraph", - description="Add a paragraph with optional styling to the document", - ) + @mcp.tool + @require_allowed("add_paragraph") + def add_paragraph(document_id: str, text: str, style: dict | None = None, return_content: bool = False): + """Add a paragraph with optional styling to the document""" + return provider.add_paragraph(document_id, text, style or {}, return_content=return_content) - mcp.tool()( - wrap( - lambda document_id, text, level, return_content=False: provider.add_heading( - document_id, text, level, return_content=return_content - ), - "add_heading", - ), - name="add_heading", - description="Add a heading to the document", - ) + @mcp.tool + @require_allowed("add_heading") + def add_heading(document_id: str, text: str, level: int, return_content: bool = False): + """Add a heading to the document""" + return provider.add_heading(document_id, text, level, return_content=return_content) - mcp.tool()( - wrap( - lambda document_id, rows, headers=None, border_style=None, col_widths=None, cell_shading=None, merges=None, return_content=False: provider.add_table( - document_id, - rows, - headers=headers, - border_style=border_style, - col_widths=col_widths, - cell_shading=cell_shading, - merges=merges, - return_content=return_content, - ), - "add_table", - ), - name="add_table", - description="Add a table to the document", - ) + @mcp.tool + @require_allowed("add_table") + def add_table( + document_id: str, + rows: list[list[str]], + headers: list[str] | None = None, + border_style: str | None = None, + col_widths: list[int] | None = None, + cell_shading: str | None = None, + merges: list[dict] | None = None, + return_content: bool = False, + ): + """Add a table to the document""" + return provider.add_table( + document_id, + rows, + headers=headers, + border_style=border_style, + col_widths=col_widths, + cell_shading=cell_shading, + merges=merges, + return_content=return_content, + ) - mcp.tool()( - wrap( - lambda document_id, page_size=None, orientation=None, margins=None, return_content=False: provider.add_section_break( - document_id, page_size, orientation, margins or {}, return_content=return_content - ), - "add_section_break", - ), - name="add_section_break", - description="Insert a section break with optional page setup", - ) + @mcp.tool + @require_allowed("add_section_break") + def add_section_break( + document_id: str, + page_size: str | None = None, + orientation: str | None = None, + margins: dict | None = None, + return_content: bool = False, + ): + """Insert a section break with optional page setup""" + return provider.add_section_break( + document_id, page_size, orientation, margins or {}, return_content=return_content + ) - mcp.tool()( - wrap( - lambda document_id, items, ordered=False, return_content=False: provider.add_list( - document_id, items, ordered, return_content=return_content - ), - "add_list", - ), - name="add_list", - description="Add a bulleted or numbered list to the document", - ) + @mcp.tool + @require_allowed("add_list") + def add_list(document_id: str, items: list[str], ordered: bool = False, return_content: bool = False): + """Add a bulleted or numbered list to the document""" + return provider.add_list(document_id, items, ordered, return_content=return_content) - mcp.tool()( - wrap( - lambda document_id, text, level=0, ordered=False, return_content=False: provider.add_list_item( - document_id, text, level, ordered, return_content=return_content - ), - "add_list_item", - ), - name="add_list_item", - description="Add a single list item with a specific level", - ) + @mcp.tool + @require_allowed("add_list_item") + def add_list_item(document_id: str, text: str, level: int = 0, ordered: bool = False, return_content: bool = False): + """Add a single list item with a specific level""" + return provider.add_list_item(document_id, text, level, ordered, return_content=return_content) - mcp.tool()( - wrap( - lambda document_id, return_content=False: provider.add_page_break(document_id, return_content=return_content), - "add_page_break", - ), - name="add_page_break", - description="Add a page break to the document", - ) + @mcp.tool + @require_allowed("add_page_break") + def add_page_break(document_id: str, return_content: bool = False): + """Add a page break to the document""" + return provider.add_page_break(document_id, return_content=return_content) - mcp.tool()( - wrap( - lambda document_id, from_level=1, to_level=3, right_align_dots=True, return_content=False: provider.insert_toc( - document_id, from_level, to_level, right_align_dots, return_content=return_content - ), - "insert_toc", - ), - name="insert_toc", - description="Insert a Table of Contents placeholder", - ) + @mcp.tool + @require_allowed("insert_toc") + def insert_toc( + document_id: str, + from_level: int = 1, + to_level: int = 3, + right_align_dots: bool = True, + return_content: bool = False, + ): + """Insert a Table of Contents placeholder""" + return provider.insert_toc( + document_id, from_level, to_level, right_align_dots, return_content=return_content + ) - mcp.tool()( - wrap( - lambda document_id, heading_text, name, return_content=False: provider.insert_bookmark_after_heading( - document_id, heading_text, name, return_content=return_content - ), - "insert_bookmark_after_heading", - ), - name="insert_bookmark_after_heading", - description="Insert a bookmark immediately after the first matching heading", - ) + @mcp.tool + @require_allowed("insert_bookmark_after_heading") + def insert_bookmark_after_heading( + document_id: str, + heading_text: str, + name: str, + return_content: bool = False, + ): + """Insert a bookmark immediately after the first matching heading""" + return provider.insert_bookmark_after_heading( + document_id, heading_text, name, return_content=return_content + ) - mcp.tool()( - wrap( - lambda document_id, text, return_content=False: provider.set_header(document_id, text, return_content=return_content), - "set_header", - ), - name="set_header", - description="Set the document header", - ) + @mcp.tool + @require_allowed("set_header") + def set_header(document_id: str, text: str, return_content: bool = False): + """Set the document header""" + return provider.set_header(document_id, text, return_content=return_content) - mcp.tool()( - wrap( - lambda document_id, text, return_content=False: provider.set_footer(document_id, text, return_content=return_content), - "set_footer", - ), - name="set_footer", - description="Set the document footer", - ) + @mcp.tool + @require_allowed("set_footer") + def set_footer(document_id: str, text: str, return_content: bool = False): + """Set the document footer""" + return provider.set_footer(document_id, text, return_content=return_content) - mcp.tool()( - wrap( - lambda document_id, location="footer", template=None, return_content=False: provider.set_page_numbering( - document_id, location, template, return_content=return_content - ), - "set_page_numbering", - ), - name="set_page_numbering", - description="Set a simple page numbering text in header or footer", - ) + @mcp.tool + @require_allowed("set_page_numbering") + def set_page_numbering( + document_id: str, + location: str = "footer", + template: str | None = None, + return_content: bool = False, + ): + """Set a simple page numbering text in header or footer""" + return provider.set_page_numbering(document_id, location, template, return_content=return_content) - mcp.tool()( - wrap( - lambda document_id, return_content=False: provider.embed_page_number_fields(document_id, return_content=return_content), - "embed_page_number_fields", - ), - name="embed_page_number_fields", - description="Replace placeholder 'Page {PAGE} of {PAGES}' with Word field codes (best-effort)", - ) + @mcp.tool + @require_allowed("embed_page_number_fields") + def embed_page_number_fields(document_id: str, return_content: bool = False): + """Replace placeholder 'Page {PAGE} of {PAGES}' with Word field codes (best-effort)""" + return provider.embed_page_number_fields(document_id, return_content=return_content) - mcp.tool()( - wrap( - lambda document_id, data_base64, width=None, height=None, alt_text=None, return_content=False: provider.add_image( - document_id, data_base64, width, height, alt_text, return_content=return_content - ), - "add_image", - ), - name="add_image", - description="Insert an image into the document", - ) + @mcp.tool + @require_allowed("add_image") + def add_image( + document_id: str, + data_base64: str, + width: int | None = None, + height: int | None = None, + alt_text: str | None = None, + return_content: bool = False, + ): + """Insert an image into the document""" + return provider.add_image(document_id, data_base64, width, height, alt_text, return_content=return_content) - mcp.tool()( - wrap( - lambda document_id, text, url, return_content=False: provider.add_hyperlink( - document_id, text, url, return_content=return_content - ), - "add_hyperlink", - ), - name="add_hyperlink", - description="Insert a hyperlink into the document", - ) + @mcp.tool + @require_allowed("add_hyperlink") + def add_hyperlink(document_id: str, text: str, url: str, return_content: bool = False): + """Insert a hyperlink into the document""" + return provider.add_hyperlink(document_id, text, url, return_content=return_content) - mcp.tool()( - wrap( - lambda document_id, find_text, replace_text, return_content=False: provider.find_and_replace( - document_id, find_text, replace_text, return_content=return_content - ), - "find_and_replace", - ), - name="find_and_replace", - description="Find and replace text in the document", - ) + @mcp.tool + @require_allowed("find_and_replace") + def find_and_replace(document_id: str, find_text: str, replace_text: str, return_content: bool = False): + """Find and replace text in the document""" + return provider.find_and_replace(document_id, find_text, replace_text, return_content=return_content) - mcp.tool()( - wrap( - lambda document_id, pattern, replacement, case_sensitive=False, whole_word=False, use_regex=False, return_content=False: provider.find_and_replace_advanced( - document_id, pattern, replacement, case_sensitive, whole_word, use_regex, return_content=return_content - ), - "find_and_replace_advanced", - ), - name="find_and_replace_advanced", - description="Find/replace with regex, case, whole-word, preserving runs", - ) + @mcp.tool + @require_allowed("find_and_replace_advanced") + def find_and_replace_advanced( + document_id: str, + pattern: str, + replacement: str, + case_sensitive: bool = False, + whole_word: bool = False, + use_regex: bool = False, + return_content: bool = False, + ): + """Find/replace with regex, case, whole-word, preserving runs""" + return provider.find_and_replace_advanced( + document_id, pattern, replacement, case_sensitive, whole_word, use_regex, return_content=return_content + ) - mcp.tool()( - wrap( - lambda document_id, contains=None, format=None, return_content=False: provider.apply_paragraph_format( - document_id, contains, format or {}, return_content=return_content - ), - "apply_paragraph_format", - ), - name="apply_paragraph_format", - description="Apply paragraph formatting to paragraphs matching a simple selector", - ) + @mcp.tool + @require_allowed("apply_paragraph_format") + def apply_paragraph_format( + document_id: str, + contains: str | None = None, + format: dict | None = None, + return_content: bool = False, + ): + """Apply paragraph formatting to paragraphs matching a simple selector""" + return provider.apply_paragraph_format(document_id, contains, format or {}, return_content=return_content) - mcp.tool()( - wrap( - lambda document_id: provider.extract_text(document_id), - "extract_text", - ), - name="extract_text", - description="Extract all text content from the document", - ) + @mcp.tool + @require_allowed("extract_text") + def extract_text(document_id: str): + """Extract all text content from the document""" + return provider.extract_text(document_id) - mcp.tool()( - wrap( - lambda document_id: provider.get_tables(document_id), - "get_tables", - ), - name="get_tables", - description="List tables with dimensions, merges, and cell content", - ) + @mcp.tool + @require_allowed("get_tables") + def get_tables(document_id: str): + """List tables with dimensions, merges, and cell content""" + return provider.get_tables(document_id) - mcp.tool()( - wrap( - lambda document_id: provider.list_images(document_id), - "list_images", - ), - name="list_images", - description="List images with width/height and alt text", - ) + @mcp.tool + @require_allowed("list_images") + def list_images(document_id: str): + """List images with width/height and alt text""" + return provider.list_images(document_id) - mcp.tool()( - wrap( - lambda document_id: provider.list_hyperlinks(document_id), - "list_hyperlinks", - ), - name="list_hyperlinks", - description="List hyperlinks in the document", - ) + @mcp.tool + @require_allowed("list_hyperlinks") + def list_hyperlinks(document_id: str): + """List hyperlinks in the document""" + return provider.list_hyperlinks(document_id) - mcp.tool()( - wrap( - lambda document_id: provider.get_fields_summary(document_id), - "get_fields_summary", - ), - name="get_fields_summary", - description="Summarize Word fields (PAGE, NUMPAGES, TOC) in document and headers/footers", - ) + @mcp.tool + @require_allowed("get_fields_summary") + def get_fields_summary(document_id: str): + """Summarize Word fields (PAGE, NUMPAGES, TOC) in document and headers/footers""" + return provider.get_fields_summary(document_id) - mcp.tool()( - wrap( - lambda document_id: provider.strip_personal_info(document_id), - "strip_personal_info", - ), - name="strip_personal_info", - description="Remove personal info from metadata and core.xml (best-effort)", - ) + @mcp.tool + @require_allowed("strip_personal_info") + def strip_personal_info(document_id: str): + """Remove personal info from metadata and core.xml (best-effort)""" + return provider.strip_personal_info(document_id) - mcp.tool()( - wrap( - lambda document_id: provider.get_metadata(document_id), - "get_metadata", - ), - name="get_metadata", - description="Get document metadata", - ) + @mcp.tool + @require_allowed("get_metadata") + def get_metadata(document_id: str): + """Get document metadata""" + return provider.get_metadata(document_id) - mcp.tool()( - wrap( - lambda document_id, output_path, return_content=True: provider.save_document( - document_id, output_path, return_content=return_content - ), - "save_document", - ), - name="save_document", - description="Save the document to a specific path and return its content", - ) + @mcp.tool + @require_allowed("save_document") + def save_document(document_id: str, output_path: str, return_content: bool = True): + """Save the document to a specific path and return its content""" + return provider.save_document(document_id, output_path, return_content=return_content) - mcp.tool()( - wrap( - lambda document_id: provider.close_document(document_id), - "close_document", - ), - name="close_document", - description="Close the document and free resources", - ) + @mcp.tool + @require_allowed("close_document") + def close_document(document_id: str): + """Close the document and free resources""" + return provider.close_document(document_id) - mcp.tool()( - wrap( - lambda: provider.list_documents(), - "list_documents", - ), - name="list_documents", - description="List all open documents", - ) + @mcp.tool + @require_allowed("list_documents") + def list_documents(): + """List all open documents""" + return provider.list_documents() - mcp.tool()( - wrap( - lambda document_id, output_path, prefer_external=False, return_content=True: provider.convert_to_pdf( - document_id, output_path, prefer_external, return_content=return_content - ), - "convert_to_pdf", - ), - name="convert_to_pdf", - description="Convert a DOCX document to PDF and return the file", - ) + @mcp.tool + @require_allowed("convert_to_pdf") + def convert_to_pdf(document_id: str, output_path: str, prefer_external: bool = False, return_content: bool = True): + """Convert a DOCX document to PDF and return the file""" + return provider.convert_to_pdf(document_id, output_path, prefer_external, return_content=return_content) - mcp.tool()( - wrap( - lambda document_id, output_path, prefer_external=True, return_content=True: provider.export_pdf_with_field_refresh( - document_id, output_path, prefer_external, return_content=return_content - ), - "export_pdf_with_field_refresh", - ), - name="export_pdf_with_field_refresh", - description="Embed page fields then export to PDF (hi-fidelity when available)", - ) + @mcp.tool + @require_allowed("export_pdf_with_field_refresh") + def export_pdf_with_field_refresh( + document_id: str, + output_path: str, + prefer_external: bool = True, + return_content: bool = True, + ): + """Embed page fields then export to PDF (hi-fidelity when available)""" + return provider.export_pdf_with_field_refresh( + document_id, output_path, prefer_external, return_content=return_content + ) - mcp.tool()( - wrap( - lambda document_id, output_dir, format="png", dpi=150, return_content=True: provider.convert_to_images( - document_id, output_dir, format, dpi, return_content=return_content - ), - "convert_to_images", - ), - name="convert_to_images", - description="Convert a DOCX document to images (one per page) and return them", - ) + @mcp.tool + @require_allowed("convert_to_images") + def convert_to_images( + document_id: str, + output_dir: str, + format: str = "png", + dpi: int = 150, + return_content: bool = True, + ): + """Convert a DOCX document to images (one per page) and return them""" + return provider.convert_to_images(document_id, output_dir, format, dpi, return_content=return_content) - mcp.tool()( - wrap( - lambda document_id, output_dir, format="png", dpi=150, prefer_external=True, return_content=True: provider.convert_to_images_with_preference( - document_id, output_dir, format, dpi, prefer_external, return_content=return_content - ), - "convert_to_images_with_preference", - ), - name="convert_to_images_with_preference", - description="Convert DOCX to images, preferring external hi-fidelity path", - ) + @mcp.tool + @require_allowed("convert_to_images_with_preference") + def convert_to_images_with_preference( + document_id: str, + output_dir: str, + format: str = "png", + dpi: int = 150, + prefer_external: bool = True, + return_content: bool = True, + ): + """Convert DOCX to images, preferring external hi-fidelity path""" + return provider.convert_to_images_with_preference( + document_id, output_dir, format, dpi, prefer_external, return_content=return_content + ) - mcp.tool()( - wrap( - lambda document_ids, output_path, return_content=True: provider.merge_documents( - document_ids, output_path, return_content=return_content - ), - "merge_documents", - ), - name="merge_documents", - description="Merge multiple DOCX documents into one and return the result", - ) + @mcp.tool + @require_allowed("merge_documents") + def merge_documents(document_ids: list[str], output_path: str, return_content: bool = True): + """Merge multiple DOCX documents into one and return the result""" + return provider.merge_documents(document_ids, output_path, return_content=return_content) - mcp.tool()( - wrap( - lambda document_id, output_dir, return_content=True: provider.split_document( - document_id, output_dir, return_content=return_content - ), - "split_document", - ), - name="split_document", - description="Split a document at page breaks and return parts", - ) + @mcp.tool + @require_allowed("split_document") + def split_document(document_id: str, output_dir: str, return_content: bool = True): + """Split a document at page breaks and return parts""" + return provider.split_document(document_id, output_dir, return_content=return_content) - mcp.tool()( - wrap( - lambda document_id: provider.get_document_structure(document_id), - "get_document_structure", - ), - name="get_document_structure", - description="Get the structural overview of the document (headings, sections, etc.)", - ) + @mcp.tool + @require_allowed("get_document_structure") + def get_document_structure(document_id: str): + """Get the structural overview of the document (headings, sections, etc.)""" + return provider.get_document_structure(document_id) - mcp.tool()( - wrap( - lambda document_id: provider.get_outline(document_id), - "get_outline", - ), - name="get_outline", - description="Return heading outline with range_ids", - ) + @mcp.tool + @require_allowed("get_outline") + def get_outline(document_id: str): + """Return heading outline with range_ids""" + return provider.get_outline(document_id) - mcp.tool()( - wrap( - lambda document_id, selector: provider.get_ranges(document_id, selector), - "get_ranges", - ), - name="get_ranges", - description="Resolve a selector to range_ids", - ) + @mcp.tool + @require_allowed("get_ranges") + def get_ranges(document_id: str, selector: str): + """Resolve a selector to range_ids""" + return provider.get_ranges(document_id, selector) - mcp.tool()( - wrap( - lambda document_id, range_id, text, return_content=False: provider.replace_range_text( - document_id, range_id, text, return_content=return_content - ), - "replace_range_text", - ), - name="replace_range_text", - description="Replace text in a paragraph/heading by range_id", - ) + @mcp.tool + @require_allowed("replace_range_text") + def replace_range_text(document_id: str, range_id: dict, text: str, return_content: bool = False): + """Replace text in a paragraph/heading by range_id""" + return provider.replace_range_text(document_id, range_id, text, return_content=return_content) - mcp.tool()( - wrap( - lambda document_id, table_index, row, col, text, return_content=False: provider.set_table_cell_text( - document_id, table_index, row, col, text, return_content=return_content - ), - "set_table_cell_text", - ), - name="set_table_cell_text", - description="Set text in a table cell by indices", - ) + @mcp.tool + @require_allowed("set_table_cell_text") + def set_table_cell_text( + document_id: str, + table_index: int, + row: int, + col: int, + text: str, + return_content: bool = False, + ): + """Set text in a table cell by indices""" + return provider.set_table_cell_text(document_id, table_index, row, col, text, return_content=return_content) - mcp.tool()( - wrap( - lambda document_id: provider.get_document_properties(document_id), - "get_document_properties", - ), - name="get_document_properties", - description="Get document properties (title, subject, author, timestamps)", - ) + @mcp.tool + @require_allowed("get_document_properties") + def get_document_properties(document_id: str): + """Get document properties (title, subject, author, timestamps)""" + return provider.get_document_properties(document_id) - mcp.tool()( - wrap( - lambda document_id, title=None, subject=None, author=None, return_content=False: provider.set_document_properties( - document_id, title, subject, author, return_content=return_content - ), - "set_document_properties", - ), - name="set_document_properties", - description="Set document properties (title, subject, author)", - ) + @mcp.tool + @require_allowed("set_document_properties") + def set_document_properties( + document_id: str, + title: str | None = None, + subject: str | None = None, + author: str | None = None, + return_content: bool = False, + ): + """Set document properties (title, subject, author)""" + return provider.set_document_properties(document_id, title, subject, author, return_content=return_content) - mcp.tool()( - wrap( - lambda document_id, heading_text, text, return_content=False: provider.insert_after_heading( - document_id, heading_text, text, return_content=return_content - ), - "insert_after_heading", - ), - name="insert_after_heading", - description="Insert a paragraph after the first heading that matches text", - ) + @mcp.tool + @require_allowed("insert_after_heading") + def insert_after_heading(document_id: str, heading_text: str, text: str, return_content: bool = False): + """Insert a paragraph after the first heading that matches text""" + return provider.insert_after_heading(document_id, heading_text, text, return_content=return_content) - mcp.tool()( - wrap( - lambda document_id: provider.sanitize_external_links(document_id), - "sanitize_external_links", - ), - name="sanitize_external_links", - description="Remove external hyperlinks (http/https)", - ) + @mcp.tool + @require_allowed("sanitize_external_links") + def sanitize_external_links(document_id: str): + """Remove external hyperlinks (http/https)""" + return provider.sanitize_external_links(document_id) - mcp.tool()( - wrap( - lambda document_id, pattern, use_regex=False, whole_word=False, case_sensitive=False, return_content=False: provider.redact_text( - document_id, pattern, use_regex, whole_word, case_sensitive, return_content=return_content - ), - "redact_text", - ), - name="redact_text", - description="Redact text using regex/whole-word with █ character", - ) + @mcp.tool + @require_allowed("redact_text") + def redact_text( + document_id: str, + pattern: str, + use_regex: bool = False, + whole_word: bool = False, + case_sensitive: bool = False, + return_content: bool = False, + ): + """Redact text using regex/whole-word with █ character""" + return provider.redact_text(document_id, pattern, use_regex, whole_word, case_sensitive, return_content=return_content) - mcp.tool()( - wrap( - lambda document_id: provider.analyze_formatting(document_id), - "analyze_formatting", - ), - name="analyze_formatting", - description="Analyze the formatting used throughout the document", - ) + @mcp.tool + @require_allowed("analyze_formatting") + def analyze_formatting(document_id: str): + """Analyze the formatting used throughout the document""" + return provider.analyze_formatting(document_id) - mcp.tool()( - wrap( - lambda document_id: provider.get_word_count(document_id), - "get_word_count", - ), - name="get_word_count", - description="Get detailed word count statistics for the document", - ) + @mcp.tool + @require_allowed("get_word_count") + def get_word_count(document_id: str): + """Get detailed word count statistics for the document""" + return provider.get_word_count(document_id) - mcp.tool()( - wrap( - lambda document_id, search_term, case_sensitive=False, whole_word=False: provider.search_text( - document_id, search_term, case_sensitive, whole_word - ), - "search_text", - ), - name="search_text", - description="Search for text patterns in the document", - ) + @mcp.tool + @require_allowed("search_text") + def search_text(document_id: str, search_term: str, case_sensitive: bool = False, whole_word: bool = False): + """Search for text patterns in the document""" + return provider.search_text(document_id, search_term, case_sensitive, whole_word) - mcp.tool()( - wrap( - lambda document_id, output_path, return_content=True: provider.export_to_markdown( - document_id, output_path, return_content=return_content - ), - "export_to_markdown", - ), - name="export_to_markdown", - description="Export document content to Markdown format and return the file", - ) + @mcp.tool + @require_allowed("export_to_markdown") + def export_to_markdown(document_id: str, output_path: str, return_content: bool = True): + """Export document content to Markdown format and return the file""" + return provider.export_to_markdown(document_id, output_path, return_content=return_content) - mcp.tool()( - wrap( - lambda document_id, output_path, return_content=True: provider.export_to_html( - document_id, output_path, return_content=return_content - ), - "export_to_html", - ), - name="export_to_html", - description="Export document content to HTML format and return the file", - ) + @mcp.tool + @require_allowed("export_to_html") + def export_to_html(document_id: str, output_path: str, return_content: bool = True): + """Export document content to HTML format and return the file""" + return provider.export_to_html(document_id, output_path, return_content=return_content) - mcp.tool()( - wrap( - lambda: provider.get_security_info(), - "get_security_info", - ), - name="get_security_info", - description="Get information about current security settings and restrictions", - ) + @mcp.tool + @require_allowed("get_security_info") + def get_security_info(): + """Get information about current security settings and restrictions""" + return provider.get_security_info() - mcp.tool()( - wrap( - lambda: provider.get_storage_info(), - "get_storage_info", - ), - name="get_storage_info", - description="Get information about temporary storage usage", - ) + @mcp.tool + @require_allowed("get_storage_info") + def get_storage_info(): + """Get information about temporary storage usage""" + return provider.get_storage_info() - mcp.tool()( - wrap( - lambda: list_templates(TEMPLATES_DIR), - "list_templates", - ), - name="list_templates", - description="List available document templates from the templates directory", - ) + @mcp.tool + @require_allowed("list_templates") + def list_templates(): + """List available document templates from the templates directory""" + return list_templates(TEMPLATES_DIR) - mcp.tool()( - wrap( - lambda name: provider.open_template(name, TEMPLATES_DIR), - "open_template", - ), - name="open_template", - description="Open a template document by name from the templates directory", - ) + @mcp.tool + @require_allowed("open_template") + def open_template(name: str): + """Open a template document by name from the templates directory""" + return provider.open_template(name, TEMPLATES_DIR) - mcp.tool()( - wrap( - lambda template_name, output_path, fields=None, return_content=True: provider.generate_from_template( - template_name, output_path, fields or {}, return_content=return_content - ), - "generate_from_template", - ), - name="generate_from_template", - description="Generate a new document from a template and return the file", - ) + @mcp.tool + @require_allowed("generate_from_template") + def generate_from_template( + template_name: str, + output_path: str, + fields: dict | None = None, + return_content: bool = True, + ): + """Generate a new document from a template and return the file""" + return provider.generate_from_template( + template_name, output_path, fields or {}, return_content=return_content + ) return mcp