#!/usr/bin/env python3
"""
Server Management Utility for Generic PDF MCP Server
Handles PDF uploads, configuration management, and server setup
"""
import argparse
import json
import os
import shutil
import sys
from pathlib import Path
from typing import List, Optional
import subprocess
from config import ConfigManager, GenericPDFServerConfig, load_config_from_env_or_file
class ServerManager:
"""Manages PDF files, configuration, and server operations"""
def __init__(self, config_path: Optional[str] = None):
self.config_path = config_path
self.config_manager = ConfigManager(config_path)
self.config: Optional[GenericPDFServerConfig] = None
def load_config(self) -> bool:
"""Load server configuration"""
try:
self.config = self.config_manager.load_config()
return True
except Exception as e:
print(f"Error loading configuration: {e}")
return False
def create_config_interactive(self) -> bool:
"""Create configuration file interactively"""
print("Creating new server configuration...")
print()
# Get server details
server_name = input("Server name (e.g., 'my-docs-server'): ").strip()
if not server_name:
print("Server name is required")
return False
display_name = input(f"Display name (default: '{server_name.replace('-', ' ').title()} PDF Server'): ").strip()
if not display_name:
display_name = f"{server_name.replace('-', ' ').title()} PDF Server"
description = input(f"Description (default: 'Search and retrieve information from {server_name} PDF documentation'): ").strip()
if not description:
description = f"Search and retrieve information from {server_name} PDF documentation"
# Get storage paths
pdf_folder = input("PDF folder path (default: './docs'): ").strip()
if not pdf_folder:
pdf_folder = "./docs"
markdown_folder = input(f"Markdown folder path (default: '{pdf_folder}/markdown'): ").strip()
if not markdown_folder:
markdown_folder = f"{pdf_folder}/markdown"
# Get domain keywords
print("\nEnter domain-specific keywords (one per line, empty line to finish):")
domain_keywords = []
while True:
keyword = input("Keyword: ").strip()
if not keyword:
break
domain_keywords.append(keyword)
# Create configuration
config = self.config_manager.create_default_config(server_name, pdf_folder)
config.server.display_name = display_name
config.server.description = description
config.storage.markdown_folder = markdown_folder
config.storage.domain_keywords = domain_keywords
# Save configuration
try:
self.config_manager.save_config(config)
self.config = config
print(f"\nConfiguration saved to: {self.config_manager.config_path}")
return True
except Exception as e:
print(f"Error saving configuration: {e}")
return False
def add_pdf(self, pdf_path: str) -> bool:
"""Add a PDF file to the server"""
if not self.config:
print("No configuration loaded. Run 'create-config' first.")
return False
source_path = Path(pdf_path)
if not source_path.exists():
print(f"PDF file not found: {pdf_path}")
return False
if not source_path.suffix.lower() == '.pdf':
print(f"File is not a PDF: {pdf_path}")
return False
# Create PDF folder if it doesn't exist
pdf_folder = Path(self.config.storage.pdf_folder)
pdf_folder.mkdir(parents=True, exist_ok=True)
# Copy PDF to server folder
dest_path = pdf_folder / source_path.name
try:
shutil.copy2(source_path, dest_path)
print(f"Added PDF: {source_path.name}")
return True
except Exception as e:
print(f"Error copying PDF: {e}")
return False
def remove_pdf(self, filename: str) -> bool:
"""Remove a PDF file from the server"""
if not self.config:
print("No configuration loaded.")
return False
pdf_folder = Path(self.config.storage.pdf_folder)
pdf_path = pdf_folder / filename
if not pdf_path.exists():
print(f"PDF file not found: {filename}")
return False
try:
pdf_path.unlink()
# Also remove corresponding markdown file if it exists
markdown_folder = Path(self.config.storage.markdown_folder)
markdown_path = markdown_folder / f"{pdf_path.stem}.md"
if markdown_path.exists():
markdown_path.unlink()
print(f"Removed markdown file: {markdown_path.name}")
print(f"Removed PDF: {filename}")
return True
except Exception as e:
print(f"Error removing PDF: {e}")
return False
def list_pdfs(self) -> List[Path]:
"""List all PDF files in the server"""
if not self.config:
print("No configuration loaded.")
return []
pdf_folder = Path(self.config.storage.pdf_folder)
if not pdf_folder.exists():
print(f"PDF folder does not exist: {pdf_folder}")
return []
pdf_files = list(pdf_folder.glob("*.pdf"))
if pdf_files:
print(f"PDF files in {pdf_folder}:")
for i, pdf_file in enumerate(pdf_files, 1):
size_mb = pdf_file.stat().st_size / (1024 * 1024)
print(f" {i}. {pdf_file.name} ({size_mb:.1f} MB)")
else:
print("No PDF files found.")
return pdf_files
def process_pdfs(self) -> bool:
"""Process all PDFs to markdown"""
if not self.config:
print("No configuration loaded.")
return False
try:
# Import and run the PDF conversion
from convert_pdfs import main as convert_main
# Temporarily modify sys.argv for convert_pdfs
original_argv = sys.argv
sys.argv = [
"convert_pdfs.py",
self.config.storage.pdf_folder,
self.config.storage.markdown_folder
]
try:
convert_main()
return True
finally:
sys.argv = original_argv
except Exception as e:
print(f"Error processing PDFs: {e}")
return False
def test_server(self) -> bool:
"""Test the server configuration"""
if not self.config:
print("No configuration loaded.")
return False
print("Testing server configuration...")
# Validate paths
errors = self.config_manager.validate_paths()
if errors:
print("Configuration errors:")
for error in errors:
print(f" - {error}")
return False
# Check for PDF files
pdf_files = self.list_pdfs()
if not pdf_files:
print("Warning: No PDF files found. Add some PDFs first.")
print("Configuration is valid!")
return True
def generate_mcp_config(self) -> str:
"""Generate MCP client configuration"""
if not self.config:
raise ValueError("No configuration loaded.")
# Get current working directory for absolute paths
current_dir = Path.cwd()
server_script = current_dir / "server.py"
# Get environment variable names
env_vars = self.config_manager.get_env_var_names()
mcp_config = {
self.config.server.name: {
"command": str(sys.executable),
"args": [str(server_script)],
"env": {
env_vars["pdf_folder"]: str(Path(self.config.storage.pdf_folder).resolve()),
env_vars["markdown_folder"]: str(Path(self.config.storage.markdown_folder).resolve())
}
}
}
return json.dumps({"mcpServers": mcp_config}, indent=2)
def main():
parser = argparse.ArgumentParser(
description="Manage Generic PDF MCP Server",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
%(prog)s create-config # Create configuration interactively
%(prog)s add-pdf document.pdf # Add a PDF to the server
%(prog)s remove-pdf document.pdf # Remove a PDF from the server
%(prog)s list-pdfs # List all PDFs
%(prog)s process-pdfs # Convert PDFs to markdown
%(prog)s test # Test server configuration
%(prog)s generate-mcp-config # Generate MCP client config
"""
)
parser.add_argument(
"--config",
help="Path to configuration file (default: ./server_config.json)"
)
subparsers = parser.add_subparsers(dest="command", help="Available commands")
# Create config command
create_parser = subparsers.add_parser("create-config", help="Create server configuration")
# Add PDF command
add_parser = subparsers.add_parser("add-pdf", help="Add PDF to server")
add_parser.add_argument("pdf_path", help="Path to PDF file")
# Remove PDF command
remove_parser = subparsers.add_parser("remove-pdf", help="Remove PDF from server")
remove_parser.add_argument("filename", help="PDF filename to remove")
# List PDFs command
list_parser = subparsers.add_parser("list-pdfs", help="List all PDFs")
# Process PDFs command
process_parser = subparsers.add_parser("process-pdfs", help="Process PDFs to markdown")
# Test command
test_parser = subparsers.add_parser("test", help="Test server configuration")
# Generate MCP config command
mcp_parser = subparsers.add_parser("generate-mcp-config", help="Generate MCP client configuration")
args = parser.parse_args()
if not args.command:
parser.print_help()
return
# Create server manager
manager = ServerManager(args.config)
if args.command == "create-config":
if manager.create_config_interactive():
print("\nNext steps:")
print("1. Add PDF files: python manage_server.py add-pdf /path/to/document.pdf")
print("2. Process PDFs: python manage_server.py process-pdfs")
print("3. Generate MCP config: python manage_server.py generate-mcp-config")
elif args.command == "add-pdf":
if manager.load_config():
manager.add_pdf(args.pdf_path)
elif args.command == "remove-pdf":
if manager.load_config():
manager.remove_pdf(args.filename)
elif args.command == "list-pdfs":
if manager.load_config():
manager.list_pdfs()
elif args.command == "process-pdfs":
if manager.load_config():
if manager.process_pdfs():
print("PDF processing completed successfully!")
else:
print("PDF processing failed.")
sys.exit(1)
elif args.command == "test":
if manager.load_config():
if not manager.test_server():
sys.exit(1)
elif args.command == "generate-mcp-config":
if manager.load_config():
try:
config_json = manager.generate_mcp_config()
print("MCP Client Configuration:")
print("=" * 50)
print(config_json)
print("=" * 50)
print("\nAdd this to your Claude Desktop configuration file:")
# Show platform-specific config file paths
import platform
system = platform.system()
if system == "Darwin": # macOS
config_path = "~/Library/Application Support/Claude/claude_desktop_config.json"
elif system == "Windows":
config_path = "%APPDATA%\\Claude\\claude_desktop_config.json"
else: # Linux
config_path = "~/.config/claude/claude_desktop_config.json"
print(f"Configuration file location: {config_path}")
except Exception as e:
print(f"Error generating MCP config: {e}")
sys.exit(1)
if __name__ == "__main__":
main()