#!/usr/bin/env python3
"""
MCP Presidio Installation Script
Conditionally installs dependencies and spaCy language models based on user preferences.
"""
import sys
import subprocess
import os
from pathlib import Path
def print_header(text):
"""Print a formatted header."""
print("\n" + "=" * 70)
print(f" {text}")
print("=" * 70)
def print_info(text):
"""Print info message."""
print(f"ℹ️ {text}")
def print_success(text):
"""Print success message."""
print(f"✅ {text}")
def print_error(text):
"""Print error message."""
print(f"❌ {text}")
def print_warning(text):
"""Print warning message."""
print(f"⚠️ {text}")
def check_python_version():
"""Check if Python version is 3.10 or higher."""
print_header("Checking Python Version")
version = sys.version_info
print_info(f"Python version: {version.major}.{version.minor}.{version.micro}")
if version.major < 3 or (version.major == 3 and version.minor < 10):
print_error("Python 3.10 or higher is required")
return False
print_success("Python version is compatible")
return True
def is_package_installed(package_name):
"""Check if a Python package is installed."""
try:
result = subprocess.run(
[sys.executable, "-m", "pip", "show", package_name],
capture_output=True,
text=True,
check=False
)
return result.returncode == 0
except Exception:
# Fallback to import check
try:
__import__(package_name.replace("-", "_"))
return True
except ImportError:
return False
def is_spacy_model_installed(model_name):
"""Check if a spaCy model is installed."""
try:
import spacy
spacy.load(model_name)
return True
except (ImportError, OSError):
return False
def run_command(command, description):
"""Run a shell command and handle errors."""
print_info(f"{description}...")
try:
# Split command into list for safer execution without shell=True
if isinstance(command, str):
command_list = command.split()
else:
command_list = command
subprocess.run(
command_list,
check=True,
capture_output=True,
text=True
)
return True
except subprocess.CalledProcessError as e:
print_error(f"Failed: {e}")
if e.stderr:
print(f" Error details: {e.stderr[:200]}")
return False
def install_base_dependencies():
"""Install base package dependencies."""
print_header("Installing Base Dependencies")
# Check if already installed
if is_package_installed("mcp") and is_package_installed("presidio_analyzer"):
print_info("Base dependencies already installed")
response = input("Reinstall? (y/N): ").strip().lower()
if response != 'y':
print_success("Skipping base dependencies")
return True
print_info("Installing mcp-presidio package...")
# Install in editable mode
if run_command([sys.executable, "-m", "pip", "install", "-e", "."], "Installing package"):
print_success("Base dependencies installed successfully")
return True
else:
print_error("Failed to install base dependencies")
return False
def install_spacy_model(model_name, language_name):
"""Install a specific spaCy model."""
if is_spacy_model_installed(model_name):
print_success(f"{language_name} model ({model_name}) already installed")
return True
print_info(f"Installing {language_name} model ({model_name})...")
if run_command([sys.executable, "-m", "spacy", "download", model_name], f"Downloading {model_name}"):
print_success(f"{language_name} model installed successfully")
return True
else:
print_warning(f"Failed to install {language_name} model")
return False
def install_language_models():
"""Install spaCy language models based on user selection."""
print_header("Installing Language Models")
print_info("spaCy language models enable accurate PII detection.")
print_info("At least one language model is required for the server to work optimally.")
print()
# Available models
models = {
"1": ("en_core_web_lg", "English", True), # Required
"2": ("es_core_news_lg", "Spanish", False),
"3": ("fr_core_news_lg", "French", False),
"4": ("de_core_news_lg", "German", False),
"5": ("it_core_news_lg", "Italian", False),
"6": ("pt_core_news_lg", "Portuguese", False),
}
print("Available language models:")
for key, (model, lang, required) in models.items():
status = "[REQUIRED]" if required else "[OPTIONAL]"
installed = "✓ Installed" if is_spacy_model_installed(model) else "✗ Not installed"
print(f" {key}. {lang:12} {status:12} - {installed}")
print("\nOptions:")
print(" a - Install all models")
print(" r - Install required models only (English)")
print(" n - Skip language model installation")
print(" 1,2,3 - Install specific models (comma-separated)")
print()
choice = input("Enter your choice: ").strip().lower()
if choice == 'n':
print_warning("Skipping language model installation")
print_warning("Note: The server will work with limited accuracy without language models")
return True
to_install = []
if choice == 'a':
to_install = list(models.keys())
elif choice == 'r':
to_install = ['1']
elif choice:
to_install = [c.strip() for c in choice.split(',') if c.strip() in models]
else:
print_warning("No valid selection made, installing required models only")
to_install = ['1']
if not to_install:
print_warning("No models selected for installation")
return True
print()
success_count = 0
for key in to_install:
model_name, language_name, _ = models[key]
if install_spacy_model(model_name, language_name):
success_count += 1
if success_count > 0:
print_success(f"Installed {success_count} language model(s)")
return True
else:
print_error("No language models were installed successfully")
return False
def install_optional_dependencies():
"""Install optional development dependencies."""
print_header("Installing Optional Dependencies")
print_info("Optional dependencies include testing tools (pytest, pytest-asyncio)")
response = input("Install optional development dependencies? (y/N): ").strip().lower()
if response != 'y':
print_info("Skipping optional dependencies")
return True
if run_command([sys.executable, "-m", "pip", "install", "-e", ".[dev]"], "Installing development dependencies"):
print_success("Optional dependencies installed successfully")
return True
else:
print_warning("Failed to install optional dependencies")
return True # Non-critical, don't fail installation
def verify_installation():
"""Verify that the installation was successful."""
print_header("Verifying Installation")
checks = [
("mcp", "MCP SDK"),
("presidio_analyzer", "Presidio Analyzer"),
("presidio_anonymizer", "Presidio Anonymizer"),
("spacy", "spaCy NLP"),
]
all_ok = True
for package, name in checks:
if is_package_installed(package):
print_success(f"{name} is installed")
else:
print_error(f"{name} is NOT installed")
all_ok = False
# Check for at least one language model
common_models = ["en_core_web_lg", "es_core_news_lg", "fr_core_news_lg", "de_core_news_lg"]
model_found = False
for model in common_models:
if is_spacy_model_installed(model):
print_success(f"Language model {model} is installed")
model_found = True
break
if not model_found:
print_warning("No spaCy language models found - limited accuracy expected")
print_info("You can install models later with: python -m spacy download en_core_web_lg")
return all_ok
def test_server():
"""Test basic server functionality."""
print_header("Testing Server")
response = input("Run a basic functionality test? (Y/n): ").strip().lower()
if response == 'n':
print_info("Skipping server test")
return True
print_info("Testing basic PII detection...")
try:
from mcp_presidio.server import analyze_text
import json
test_text = "My email is test@example.com"
result = analyze_text(test_text)
result_obj = json.loads(result)
if len(result_obj) > 0:
print_success("Server test passed - PII detection working!")
print_info(f"Detected {len(result_obj)} entity/entities")
for entity in result_obj:
print(f" - {entity['entity_type']}: '{entity['text']}'")
else:
print_warning("Test completed but no PII detected")
print_info("This may indicate language models are not installed")
return True
except Exception as e:
print_error(f"Server test failed: {e}")
return False
def print_next_steps():
"""Print next steps after installation."""
print_header("Installation Complete!")
print("\n📚 Next Steps:\n")
print("1. Start the server:")
print(" $ mcp-presidio")
print(" or")
print(" $ python -m mcp_presidio.server")
print()
print("2. Configure Claude Desktop:")
print(" See CLAUDE_CONFIG.md for configuration instructions")
print()
print("3. Read the documentation:")
print(" - README.md - Complete overview")
print(" - QUICKSTART.md - Get started quickly")
print(" - EXAMPLES.md - Usage examples")
print()
print("4. Test with example:")
print(' $ python -c "from mcp_presidio.server import analyze_text; print(analyze_text(\\"test@example.com\\"))"')
print()
print("📖 Documentation: https://github.com/cmalpass/mcp-presidio")
print()
def main():
"""Main installation function."""
print_header("MCP Presidio Installation")
print_info("This script will help you install MCP Presidio and its dependencies")
print()
# Change to script directory
script_dir = Path(__file__).parent
os.chdir(script_dir)
# Check Python version
if not check_python_version():
sys.exit(1)
# Install base dependencies
if not install_base_dependencies():
print_error("Failed to install base dependencies")
sys.exit(1)
# Install language models
install_language_models()
# Install optional dependencies
install_optional_dependencies()
# Verify installation
if not verify_installation():
print_warning("Installation completed with some issues")
print_info("You may need to manually install missing components")
# Test server
test_server()
# Print next steps
print_next_steps()
print_success("Setup complete! 🎉")
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
print("\n\n⚠️ Installation cancelled by user")
sys.exit(1)
except Exception as e:
print_error(f"Unexpected error: {e}")
sys.exit(1)