# Directory Structure ``` ├── .gitignore ├── claude_desktop_config.json ├── claude_document_mcp │ ├── __init__.py │ ├── __main__.py │ └── server.py ├── CLAUDE_INTEGRATION.md ├── pyproject.toml ├── README.md ├── requirements.txt ├── run.py ├── run.sh ├── setup.sh ├── start_server.sh ├── test_client.py ├── test_server.sh ├── uv.lock └── verify.py ``` # Files -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- ``` # Python __pycache__/ *.py[cod] *$py.class *.so .Python .venv/ env/ build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ *.egg-info/ .installed.cfg *.egg # Environment variables .env .env.* # Logs logs/ *.log # macOS .DS_Store .AppleDouble .LSOverride ._* # VS Code .vscode/ # PyCharm .idea/ # Jupiter .ipynb_checkpoints # Testing .pytest_cache/ htmlcov/ .coverage ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- ```markdown # Claude Document MCP Server A Model Context Protocol (MCP) server that allows Claude Desktop to perform document operations on Microsoft Word, Excel, and PDF files. ## Features ### Microsoft Word Operations - Create new Word documents from text - Edit existing Word documents (add/edit/delete paragraphs and headings) - Convert text files (.txt) to Word documents ### Excel Operations - Create new Excel spreadsheets from JSON or CSV-like text - Edit existing Excel files (update cells, ranges, add/delete rows, columns, sheets) - Convert CSV files to Excel ### PDF Operations - Create new PDF files from text - Convert Word documents to PDF files ## Setup This MCP server requires Python 3.10 or higher. ### Automatic Setup (Recommended) Run the setup script to automatically install dependencies and configure for Claude Desktop: ```bash git clone https://github.com/alejandroBallesterosC/document-edit-mcp cd document-edit-mcp ./setup.sh ``` This will: 1. Create a virtual environment 2. Install required dependencies 3. Configure the server for Claude Desktop 4. Create necessary directories ### Manual Setup If you prefer to set up manually: 1. Install dependencies: ```bash cd claude-document-mcp python -m venv .venv source .venv/bin/activate # On Windows: .venv\Scripts\activate pip install -e . ``` 2. Configure Claude Desktop: Copy the `claude_desktop_config.json` file to: - **Mac**: `~/Library/Application Support/Claude/` - **Windows**: `%APPDATA%\Claude\` 3. Restart Claude Desktop ## Model Context Protocol Integration This server follows the Model Context Protocol specification to provide document manipulation capabilities for Claude Desktop: - **Tools**: Provides manipulations functions for Word, Excel, and PDF operations - **Resources**: Provides information about capabilities - **Prompts**: (none currently implemented) ## API Reference ### Microsoft Word #### Create a Word Document ``` create_word_document(filepath: str, content: str) -> Dict ``` #### Edit a Word Document ``` edit_word_document(filepath: str, operations: List[Dict]) -> Dict ``` #### Convert TXT to Word ``` convert_txt_to_word(source_path: str, target_path: str) -> Dict ``` ### Excel #### Create an Excel File ``` create_excel_file(filepath: str, content: str) -> Dict ``` #### Edit an Excel File ``` edit_excel_file(filepath: str, operations: List[Dict]) -> Dict ``` #### Convert CSV to Excel ``` convert_csv_to_excel(source_path: str, target_path: str) -> Dict ``` ### PDF #### Create a PDF File ``` create_pdf_file(filepath: str, content: str) -> Dict ``` #### Convert Word to PDF ``` convert_word_to_pdf(source_path: str, target_path: str) -> Dict ``` ## Logs The server logs all operations to both the console and a `logs/document_mcp.log` file for troubleshooting. ## License MIT ## Contributing Contributions are welcome! Please feel free to submit a Pull Request. ``` -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- ``` uvicorn fastapi python-docx pandas openpyxl reportlab pdf2docx docx2pdf ``` -------------------------------------------------------------------------------- /claude_document_mcp/__init__.py: -------------------------------------------------------------------------------- ```python """ Claude Document MCP - A Model Context Protocol server for document operations """ __version__ = "0.1.0" ``` -------------------------------------------------------------------------------- /claude_document_mcp/__main__.py: -------------------------------------------------------------------------------- ```python """ Claude Document MCP - CLI entry point """ import sys from claude_document_mcp.server import main if __name__ == "__main__": sys.exit(main()) ``` -------------------------------------------------------------------------------- /run.py: -------------------------------------------------------------------------------- ```python #!/usr/bin/env python3 """ Run script for Claude Document MCP Server """ from claude_document_mcp.server import main if __name__ == "__main__": main() ``` -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- ```bash #!/bin/bash # Run the Claude Document MCP Server directly # Get the project directory PROJECT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) # Run the server with UV (using --project parameter) echo "Starting Document MCP Server..." uv run --project "$PROJECT_DIR" -m claude_document_mcp.server ``` -------------------------------------------------------------------------------- /claude_desktop_config.json: -------------------------------------------------------------------------------- ```json { "mcpServers": { "document_operations": { "command": "python", "args": [ "-m", "claude_document_mcp.server" ], "cwd": "/Users/jandro/Documents/Coding-Projects/mcp-servers/claude-document-mcp", "env": { "PYTHONUNBUFFERED": "1" } } } } ``` -------------------------------------------------------------------------------- /start_server.sh: -------------------------------------------------------------------------------- ```bash #!/bin/bash # Start the Claude Document MCP Server # Activate the virtual environment if [ -d ".venv" ]; then source .venv/bin/activate else echo "Creating virtual environment with UV..." uv venv source .venv/bin/activate echo "Installing dependencies with UV..." uv pip install -e . fi # Start the server echo "Starting Document MCP Server..." python -m claude_document_mcp ``` -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- ```toml [build-system] requires = ["hatchling"] build-backend = "hatchling.build" [project] name = "claude-document-mcp" version = "0.1.0" description = "Model Context Protocol server for document operations with Claude Desktop" readme = "README.md" requires-python = ">=3.10" license = { text = "MIT" } authors = [ { name = "Your Name", email = "[email protected]" } ] dependencies = [ "mcp[cli]>=1.5.0", "python-docx>=0.8.11", "pandas>=2.0.0", "openpyxl>=3.1.0", "reportlab>=3.6.0", "pdf2docx>=0.5.6", "docx2pdf>=0.1.8" ] [project.optional-dependencies] dev = [ "pytest>=7.0.0", "black>=22.0.0", "isort>=5.10.0" ] [tool.hatch.build.targets.wheel] packages = ["claude_document_mcp"] ``` -------------------------------------------------------------------------------- /setup.sh: -------------------------------------------------------------------------------- ```bash #!/bin/bash # Setup script for Claude Document MCP Server set -e # Exit on error echo "Setting up Claude Document MCP Server..." # Check Python version python_version=$(python3 -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")') min_version="3.10" if [ "$(printf '%s\n' "$min_version" "$python_version" | sort -V | head -n1)" != "$min_version" ]; then echo "Error: Python $min_version or higher is required" echo "Current version: $python_version" exit 1 fi # Create virtual environment first (using UV) echo "Creating virtual environment with UV..." uv sync # Now install the project in development mode echo "Installing project in development mode with UV..." uv pip install -e . # Create logs directory mkdir -p logs # Get the absolute path to the project directory PROJECT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) echo "" echo "Setup complete! You can now use the Document MCP Server with Claude Desktop." echo "Start Claude Desktop to use the document tools." echo "" ``` -------------------------------------------------------------------------------- /test_server.sh: -------------------------------------------------------------------------------- ```bash #!/bin/bash # This script tests the Document MCP Server by running it and confirming it works # Create temporary directory for test files TEST_DIR=$(mktemp -d) echo "Created test directory: $TEST_DIR" # Create a test text file TEXT_FILE="$TEST_DIR/test.txt" echo "This is a test text file." > "$TEXT_FILE" echo "It has multiple lines." >> "$TEXT_FILE" echo "We'll convert it to Word." >> "$TEXT_FILE" echo "Created text file: $TEXT_FILE" # The main test process # This will start the server in the background and run tests against it echo "Starting Document MCP Server validation..." # Start the server with the MCP dev tool (this includes the inspector) echo "Starting server..." mcp dev claude_document_mcp/server.py:mcp & SERVER_PID=$! # Wait for server to start sleep 3 echo "Server started with PID: $SERVER_PID" echo "To view the server in the MCP Inspector, visit: http://localhost:9000" echo "" echo "Press Ctrl+C to stop the server when you're done testing" # Keep the script running so the server stays up wait $SERVER_PID # Clean up echo "Cleaning up test files..." rm -rf "$TEST_DIR" echo "Done." ``` -------------------------------------------------------------------------------- /CLAUDE_INTEGRATION.md: -------------------------------------------------------------------------------- ```markdown # Integrating with Claude Desktop To integrate this Document MCP Server with Claude Desktop, follow these steps: ## 1. Install the Server First, make sure you have installed the server with dependencies: ```bash cd claude-document-mcp uv venv source .venv/bin/activate # On Windows: .venv\Scripts\activate uv pip install -e . ``` ## 2. Configure Claude Desktop 1. Find your Claude Desktop configuration file: - **Mac**: `~/Library/Application Support/Claude/claude_desktop_config.json` - **Windows**: `%APPDATA%\Claude\claude_desktop_config.json` 2. Create or edit this file to include the following configuration: ```json { "mcpServers": { "document_operations": { "command": "uv", "args": [ "--directory", "/ABSOLUTE/PATH/TO/claude-document-mcp", "run", "mcp", "run", "claude_document_mcp/server.py:mcp" ] } } } ``` Replace `/ABSOLUTE/PATH/TO/claude-document-mcp` with the actual absolute path to your project directory. 3. Restart Claude Desktop ## 3. Test the Integration 1. Open Claude Desktop 2. You should see a hammer icon in the UI if the MCP server is detected 3. Try a test request like: "Can you create a new Word document with a simple hello world message and save it to my Desktop?" ## Troubleshooting If Claude Desktop doesn't detect the server: 1. Check the logs directory for errors 2. Verify your Claude Desktop config file has the correct path 3. Make sure the MCP server runs correctly on its own (use `./test_server.sh`) 4. Restart Claude Desktop after any changes ``` -------------------------------------------------------------------------------- /test_client.py: -------------------------------------------------------------------------------- ```python #!/usr/bin/env python3 """ Simple test client for the Document MCP Server """ import asyncio import json import os import sys import tempfile from pathlib import Path from contextlib import asynccontextmanager from mcp import ClientSession from mcp.client.stdio import StdioServerParameters, stdio_client async def test_word_operations(session): print("\n\nTesting Word operations...") # Create a temp directory for test files with tempfile.TemporaryDirectory() as temp_dir: # Create a text file txt_path = os.path.join(temp_dir, "test.txt") with open(txt_path, "w") as f: f.write("This is a test text file.\nIt has multiple lines.\nWe'll convert it to Word.") # Create a Word document word_path = os.path.join(temp_dir, "test_created.docx") print("Creating Word document...") result = await session.call_tool( "create_word_document", {"filepath": word_path, "content": "This is a test Word document created by the MCP server."} ) print(f"Result: {json.dumps(result, indent=2)}") if result.get("success"): # Edit the Word document print("\nEditing Word document...") result = await session.call_tool( "edit_word_document", { "filepath": word_path, "operations": [ {"type": "add_paragraph", "text": "This is a new paragraph."}, {"type": "add_heading", "text": "Test Heading", "level": 1} ] } ) print(f"Result: {json.dumps(result, indent=2)}") # Convert text to Word word_converted_path = os.path.join(temp_dir, "test_converted.docx") print("\nConverting TXT to Word...") result = await session.call_tool( "convert_txt_to_word", {"source_path": txt_path, "target_path": word_converted_path} ) print(f"Result: {json.dumps(result, indent=2)}") async def test_capabilities(session): print("\n\nGetting server capabilities...") result = await session.get_resource("capabilities://") print(f"Capabilities: {json.dumps(json.loads(result), indent=2)}") @asynccontextmanager async def connect_to_server(): """Helper to connect to the MCP server.""" server_params = StdioServerParameters( command="python", args=["-m", "claude_document_mcp.server"], cwd=os.path.dirname(os.path.abspath(__file__)) ) client = stdio_client(server_params) session = await client.connect() try: yield session finally: await session.close() async def main(): print("Document MCP Server Test Client") print("===============================") try: # Connect to the MCP server using stdio print("Connecting to server...") async with connect_to_server() as session: print("Connected!") # Get server capabilities await test_capabilities(session) # Test Word operations await test_word_operations(session) print("\nAll tests completed!") except Exception as e: print(f"Error: {str(e)}") import traceback traceback.print_exc() if __name__ == "__main__": asyncio.run(main()) ``` -------------------------------------------------------------------------------- /verify.py: -------------------------------------------------------------------------------- ```python #!/usr/bin/env python3 """ Verification script for Claude Document MCP Server This script verifies that the environment is correctly set up and that all dependencies are installed properly. """ import sys import importlib import os import subprocess from pathlib import Path def run_uv_command(args): """Run a UV command and return the output.""" cmd = ["uv"] + args try: return subprocess.check_output(cmd, universal_newlines=True) except subprocess.CalledProcessError as e: print(f"Error running UV command: {e}") return None def check_dependency(module_name): """Check if a Python module is installed.""" try: importlib.import_module(module_name) return True except ImportError: return False def main(): """Main verification function.""" print("Claude Document MCP Server Verification") print("======================================") # Check if UV is installed try: uv_version = subprocess.check_output(["uv", "--version"], universal_newlines=True).strip() print(f"UV installed: Yes (version {uv_version})") except (subprocess.CalledProcessError, FileNotFoundError): print("UV installed: No") print("ERROR: UV is not installed or not in PATH. Please install UV first.") return False # Check Python version print(f"Python version: {sys.version}") # Check if the project is installed print("\nChecking dependencies...") # Try to import the project can_import = check_dependency("claude_document_mcp") print(f"claude_document_mcp importable: {'Yes' if can_import else 'No'}") # Check MCP dependency if check_dependency("mcp"): import mcp print(f"MCP installed: Yes (version {getattr(mcp, '__version__', 'unknown')})") else: print("MCP installed: No") # Check other dependencies dependencies = [ "docx", "pandas", "openpyxl", "reportlab", "pdf2docx", "docx2pdf" ] missing_deps = [] for dep in dependencies: installed = check_dependency(dep) print(f"{dep} installed: {'Yes' if installed else 'No'}") if not installed: missing_deps.append(dep) if missing_deps: print(f"\nMissing dependencies: {', '.join(missing_deps)}") print("Run 'uv sync' to install missing dependencies") # Check if logs directory exists logs_dir = Path(__file__).parent / "logs" if logs_dir.exists(): print(f"\nLogs directory exists: Yes ({logs_dir})") else: print(f"\nLogs directory exists: No") print(f"Creating logs directory at {logs_dir}") logs_dir.mkdir(exist_ok=True) # Check if Claude Desktop config exists if sys.platform == "darwin": config_path = Path.home() / "Library/Application Support/Claude/claude_desktop_config.json" elif sys.platform == "win32": config_path = Path(os.environ.get("APPDATA", "")) / "Claude/claude_desktop_config.json" else: config_path = Path(__file__).parent / "claude_desktop_config.json" if config_path.exists(): print(f"Claude Desktop config exists: Yes ({config_path})") else: print(f"Claude Desktop config exists: No") print(f"ERROR: Claude Desktop config does not exist at {config_path}") print("Run './setup.sh' to create the configuration") return False # Test running the server with UV print("\nTesting MCP server execution with UV...") try: # Just check if the command would run, don't actually run it cmd = ["uv", "run", "-m", "claude_document_mcp.server", "--help"] subprocess.check_call(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) print("Server command is executable: Yes") except subprocess.CalledProcessError: print("Server command is executable: No") print("ERROR: Cannot execute the MCP server") return False print("\nVerification successful! Your environment is properly set up.") print("To run the server, use: ./run.sh") print("Or directly with UV: uv run -m claude_document_mcp.server") return True if __name__ == "__main__": success = main() sys.exit(0 if success else 1) ``` -------------------------------------------------------------------------------- /claude_document_mcp/server.py: -------------------------------------------------------------------------------- ```python """ Claude Document MCP Server - Model Context Protocol server for Claude Desktop Features: - Microsoft Word file operations (create, edit, convert from txt) - Excel file operations (create, edit, convert from csv) - PDF file operations (create, convert from Word) This is a headless server with no UI, designed to be used with Claude Desktop. """ import os import sys import json import logging from pathlib import Path from typing import Dict, Any, List, Optional from mcp.server.fastmcp import FastMCP # Document processing libraries try: import docx from docx import Document from docx.shared import Pt, Inches except ImportError: raise ImportError("Please install python-docx with: uv pip install python-docx") try: import pandas as pd import openpyxl except ImportError: raise ImportError("Please install pandas and openpyxl with: uv pip install pandas openpyxl") try: from reportlab.lib.pagesizes import letter from reportlab.pdfgen import canvas except ImportError: raise ImportError("Please install reportlab with: uv pip install reportlab") try: import docx2pdf except ImportError: raise ImportError("Please install docx2pdf with: uv pip install docx2pdf") # Set up logging log_dir = Path(__file__).parent.parent / "logs" log_dir.mkdir(exist_ok=True) log_file = log_dir / "document_mcp.log" logging.basicConfig( level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s", handlers=[ logging.StreamHandler(), logging.FileHandler(log_file) ] ) logger = logging.getLogger(__name__) # Initialize the FastMCP server # Make sure to use a standard variable name that can be discovered automatically server = FastMCP( "Document Operations", description="MCP server for document operations (Word, Excel, PDF)", dependencies=[ "python-docx", "pandas", "openpyxl", "reportlab", "docx2pdf", ] ) # Also expose as mcp for current code compatibility mcp = server # ---- Microsoft Word Operations ---- @server.tool() def create_word_document(filepath: str, content: str) -> Dict[str, Any]: """ Create a new Microsoft Word document with the provided content. Args: filepath: Path where to save the document content: Text content for the document Returns: Operation result with success status, message, and filepath """ try: # Create a new document doc = Document() # Add content doc.add_paragraph(content) # Ensure the directory exists os.makedirs(os.path.dirname(os.path.abspath(filepath)), exist_ok=True) # Save the document doc.save(filepath) logger.info(f"Created Word document: {filepath}") return { "success": True, "message": "Successfully created Word document", "filepath": filepath } except Exception as e: logger.error(f"Error creating Word document: {str(e)}") return { "success": False, "message": f"Error creating Word document: {str(e)}", "filepath": None } @server.tool() def edit_word_document(filepath: str, operations: List[Dict[str, Any]]) -> Dict[str, Any]: """ Edit an existing Microsoft Word document using the specified operations. Args: filepath: Path to the Word document operations: List of operations to perform, where each operation is a dictionary with: - type: Operation type (add_paragraph, add_heading, edit_paragraph, delete_paragraph) - Additional parameters depending on the operation type Returns: Operation result with success status, message, and filepath """ try: # Load the document if not os.path.exists(filepath): return { "success": False, "message": f"File not found: {filepath}", "filepath": None } doc = Document(filepath) # Apply operations for op in operations: op_type = op.get("type") if op_type == "add_paragraph": doc.add_paragraph(op.get("text", "")) elif op_type == "add_heading": doc.add_heading(op.get("text", ""), level=op.get("level", 1)) elif op_type == "edit_paragraph": idx = op.get("index", 0) new_text = op.get("text", "") if 0 <= idx < len(doc.paragraphs): doc.paragraphs[idx].text = new_text else: logger.warning(f"Paragraph index out of range: {idx}") elif op_type == "delete_paragraph": idx = op.get("index", 0) if 0 <= idx < len(doc.paragraphs): p = doc.paragraphs[idx] p_elem = p._element p_elem.getparent().remove(p_elem) else: logger.warning(f"Paragraph index out of range: {idx}") else: logger.warning(f"Unknown operation type: {op_type}") # Save the document doc.save(filepath) logger.info(f"Edited Word document: {filepath}") return { "success": True, "message": "Successfully edited Word document", "filepath": filepath } except Exception as e: logger.error(f"Error editing Word document: {str(e)}") return { "success": False, "message": f"Error editing Word document: {str(e)}", "filepath": None } @server.tool() def convert_txt_to_word(source_path: str, target_path: str) -> Dict[str, Any]: """ Convert a text file to a Microsoft Word document. Args: source_path: Path to the text file target_path: Path where to save the Word document Returns: Operation result with success status, message, and filepath """ try: # Check if source file exists if not os.path.exists(source_path): return { "success": False, "message": f"Source file not found: {source_path}", "filepath": None } # Read the text file with open(source_path, 'r', encoding='utf-8') as file: text_content = file.read() # Create a new document doc = Document() # Add content as paragraphs (split by newlines) for paragraph in text_content.split('\n'): if paragraph.strip(): # Skip empty paragraphs doc.add_paragraph(paragraph) # Ensure the directory exists os.makedirs(os.path.dirname(os.path.abspath(target_path)), exist_ok=True) # Save the document doc.save(target_path) logger.info(f"Converted text to Word: {source_path} -> {target_path}") return { "success": True, "message": "Successfully converted text to Word document", "filepath": target_path } except Exception as e: logger.error(f"Error converting text to Word: {str(e)}") return { "success": False, "message": f"Error converting text to Word: {str(e)}", "filepath": None } # ---- Excel Operations ---- @server.tool() def create_excel_file(filepath: str, content: str) -> Dict[str, Any]: """ Create a new Excel file with the provided content. Args: filepath: Path where to save the Excel file content: Data content, either JSON string or CSV-like string Returns: Operation result with success status, message, and filepath """ try: # Parse the content as JSON data try: data = json.loads(content) except json.JSONDecodeError: # If not valid JSON, treat as CSV data = [line.split(',') for line in content.strip().split('\n')] # Convert to DataFrame df = pd.DataFrame(data) # Ensure the directory exists os.makedirs(os.path.dirname(os.path.abspath(filepath)), exist_ok=True) # Save to Excel df.to_excel(filepath, index=False) logger.info(f"Created Excel file: {filepath}") return { "success": True, "message": "Successfully created Excel file", "filepath": filepath } except Exception as e: logger.error(f"Error creating Excel file: {str(e)}") return { "success": False, "message": f"Error creating Excel file: {str(e)}", "filepath": None } @server.tool() def edit_excel_file(filepath: str, operations: List[Dict[str, Any]]) -> Dict[str, Any]: """ Edit an existing Excel file using the specified operations. Args: filepath: Path to the Excel file operations: List of operations to perform, where each operation is a dictionary with: - type: Operation type (update_cell, update_range, delete_row, delete_column, add_sheet, delete_sheet) - Additional parameters depending on the operation type Returns: Operation result with success status, message, and filepath """ try: # Check if file exists if not os.path.exists(filepath): return { "success": False, "message": f"File not found: {filepath}", "filepath": None } # Load the Excel file wb = openpyxl.load_workbook(filepath) # Apply operations for op in operations: op_type = op.get("type") sheet_name = op.get("sheet", wb.sheetnames[0]) # Get the sheet, create if it doesn't exist if sheet_name not in wb.sheetnames: wb.create_sheet(sheet_name) sheet = wb[sheet_name] if op_type == "update_cell": row = op.get("row", 1) col = op.get("col", 1) value = op.get("value", "") sheet.cell(row=row, column=col, value=value) elif op_type == "update_range": start_row = op.get("start_row", 1) start_col = op.get("start_col", 1) values = op.get("values", []) for i, row_values in enumerate(values): for j, value in enumerate(row_values): sheet.cell(row=start_row + i, column=start_col + j, value=value) elif op_type == "delete_row": row = op.get("row", 1) sheet.delete_rows(row) elif op_type == "delete_column": col = op.get("col", 1) sheet.delete_cols(col) elif op_type == "add_sheet": new_sheet_name = op.get("name", "NewSheet") if new_sheet_name not in wb.sheetnames: wb.create_sheet(new_sheet_name) elif op_type == "delete_sheet": if sheet_name in wb.sheetnames and len(wb.sheetnames) > 1: del wb[sheet_name] else: logger.warning(f"Unknown operation type: {op_type}") # Save the workbook wb.save(filepath) logger.info(f"Edited Excel file: {filepath}") return { "success": True, "message": "Successfully edited Excel file", "filepath": filepath } except Exception as e: logger.error(f"Error editing Excel file: {str(e)}") return { "success": False, "message": f"Error editing Excel file: {str(e)}", "filepath": None } @server.tool() def convert_csv_to_excel(source_path: str, target_path: str) -> Dict[str, Any]: """ Convert a CSV file to an Excel file. Args: source_path: Path to the CSV file target_path: Path where to save the Excel file Returns: Operation result with success status, message, and filepath """ try: # Check if source file exists if not os.path.exists(source_path): return { "success": False, "message": f"Source file not found: {source_path}", "filepath": None } # Read the CSV file df = pd.read_csv(source_path) # Ensure the directory exists os.makedirs(os.path.dirname(os.path.abspath(target_path)), exist_ok=True) # Save to Excel df.to_excel(target_path, index=False) logger.info(f"Converted CSV to Excel: {source_path} -> {target_path}") return { "success": True, "message": "Successfully converted CSV to Excel", "filepath": target_path } except Exception as e: logger.error(f"Error converting CSV to Excel: {str(e)}") return { "success": False, "message": f"Error converting CSV to Excel: {str(e)}", "filepath": None } # ---- PDF Operations ---- @server.tool() def create_pdf_file(filepath: str, content: str) -> Dict[str, Any]: """ Create a new PDF file with the provided text content. Args: filepath: Path where to save the PDF file content: Text content for the PDF Returns: Operation result with success status, message, and filepath """ try: # Ensure the directory exists os.makedirs(os.path.dirname(os.path.abspath(filepath)), exist_ok=True) # Create a new PDF with ReportLab c = canvas.Canvas(filepath, pagesize=letter) width, height = letter # Process text content lines = content.split('\n') y_position = height - 40 # Start position from top for line in lines: if y_position < 40: # If we're at the bottom of the page c.showPage() # Create a new page y_position = height - 40 # Reset position c.drawString(40, y_position, line) y_position -= 15 # Move down for next line c.save() logger.info(f"Created PDF file: {filepath}") return { "success": True, "message": "Successfully created PDF file", "filepath": filepath } except Exception as e: logger.error(f"Error creating PDF file: {str(e)}") return { "success": False, "message": f"Error creating PDF file: {str(e)}", "filepath": None } @server.tool() def convert_word_to_pdf(source_path: str, target_path: str) -> Dict[str, Any]: """ Convert a Microsoft Word document to a PDF file. Args: source_path: Path to the Word document target_path: Path where to save the PDF file Returns: Operation result with success status, message, and filepath """ try: # Check if source file exists if not os.path.exists(source_path): return { "success": False, "message": f"Source file not found: {source_path}", "filepath": None } # Ensure the directory exists os.makedirs(os.path.dirname(os.path.abspath(target_path)), exist_ok=True) # Convert Word to PDF using docx2pdf docx2pdf.convert(source_path, target_path) logger.info(f"Converted Word to PDF: {source_path} -> {target_path}") return { "success": True, "message": "Successfully converted Word to PDF", "filepath": target_path } except Exception as e: logger.error(f"Error converting Word to PDF: {str(e)}") return { "success": False, "message": f"Error converting Word to PDF: {str(e)}", "filepath": None } # ---- Resources ---- @server.resource("capabilities://") def get_capabilities() -> Dict[str, Any]: """ Provide information about this MCP server's capabilities. Returns: Dictionary containing capabilities information """ return { "name": "Document Operations", "version": "0.1.0", "description": "Model Context Protocol server for document operations (Word, Excel, PDF)", "document_operations": { "word": { "create": True, "edit": True, "convert_from_txt": True }, "excel": { "create": True, "edit": True, "convert_from_csv": True }, "pdf": { "create": True, "convert_from_word": True } } } def main(): """Main entry point for the server.""" try: # Setup logging directory log_dir = Path(__file__).parent.parent / "logs" log_dir.mkdir(exist_ok=True) # Log to file instead of stdout startup_logger = logging.getLogger("startup") startup_logger.setLevel(logging.INFO) # Make sure startup logger doesn't also log to console startup_logger.propagate = False # Add file handler for startup logs startup_log_file = log_dir / "startup.log" file_handler = logging.FileHandler(startup_log_file) file_handler.setFormatter(logging.Formatter("%(asctime)s [%(levelname)s] %(message)s")) startup_logger.addHandler(file_handler) # Log startup information to file only startup_logger.info("Starting Document Operations MCP Server...") startup_logger.info(f"Python version: {sys.version}") startup_logger.info(f"Python executable: {sys.executable}") startup_logger.info(f"Working directory: {os.getcwd()}") startup_logger.info(f"Logs directory: {log_dir}") # Verify environment if sys.prefix == sys.base_prefix: startup_logger.warning("Not running in a virtual environment") startup_logger.info("Server is ready to accept connections from Claude Desktop!") # Run the server server.run() except Exception as e: logger.error(f"Error starting server: {str(e)}") import traceback logger.error(traceback.format_exc()) sys.exit(1) if __name__ == "__main__": main() ```