validate_configuration_complete.py•15.8 kB
#!/usr/bin/env python3
"""
Comprehensive Configuration Validation Script for MCP Memory Service
This unified script validates all configuration aspects:
- Claude Code global configuration (~/.claude.json)
- Claude Desktop configuration (claude_desktop_config.json)
- Project .env file configuration
- Cross-configuration consistency
- API token validation
- Cloudflare credentials validation
Consolidates functionality from validate_config.py and validate_configuration.py
"""
import os
import sys
import json
import re
import logging
from pathlib import Path
from typing import Dict, Any, Optional, List, Tuple
# Configure logging
logging.basicConfig(level=logging.INFO, format='%(levelname)s: %(message)s')
logger = logging.getLogger(__name__)
class ComprehensiveConfigValidator:
"""Unified configuration validator for all MCP Memory Service configurations."""
def __init__(self):
"""Initialize validator with all configuration paths and requirements."""
self.project_root = Path(__file__).parent.parent.parent
self.env_file = self.project_root / '.env'
# Platform-specific Claude Desktop config paths
if os.name == 'nt': # Windows
self.claude_desktop_config_file = Path.home() / 'AppData' / 'Roaming' / 'Claude' / 'claude_desktop_config.json'
else: # macOS/Linux
self.claude_desktop_config_file = Path.home() / '.config' / 'claude' / 'claude_desktop_config.json'
# Claude Code global config (different from Claude Desktop)
self.claude_code_config_file = Path.home() / '.claude.json'
# Local project MCP config (should usually not exist for memory service)
self.local_mcp_config_file = self.project_root / '.mcp.json'
# Required environment variables for Cloudflare backend
self.required_vars = [
'MCP_MEMORY_STORAGE_BACKEND',
'CLOUDFLARE_API_TOKEN',
'CLOUDFLARE_ACCOUNT_ID',
'CLOUDFLARE_D1_DATABASE_ID',
'CLOUDFLARE_VECTORIZE_INDEX'
]
# Optional but commonly used variables
self.optional_vars = [
'MCP_MEMORY_BACKUPS_PATH',
'MCP_MEMORY_SQLITE_PATH'
]
# Results tracking
self.issues = []
self.error_count = 0
self.warning_count = 0
self.success_count = 0
def load_json_safe(self, file_path: Path) -> Optional[Dict]:
"""Load JSON file safely, return None if not found or invalid."""
try:
if file_path.exists():
with open(file_path, 'r', encoding='utf-8') as f:
return json.load(f)
except (json.JSONDecodeError, FileNotFoundError, PermissionError) as e:
self.add_warning(f"Could not load {file_path}: {e}")
return None
def load_env_file(self) -> Dict[str, str]:
"""Load environment variables from .env file."""
env_vars = {}
if not self.env_file.exists():
self.add_warning(f"No .env file found at {self.env_file}")
return env_vars
try:
with open(self.env_file, 'r', encoding='utf-8') as f:
for line_num, line in enumerate(f, 1):
line = line.strip()
if line and not line.startswith('#') and '=' in line:
key, value = line.split('=', 1)
env_vars[key.strip()] = value.strip()
except Exception as e:
self.add_error(f"Failed to load .env file: {e}")
return env_vars
def add_error(self, message: str):
"""Add error message and increment counter."""
self.issues.append(f"ERROR: {message}")
self.error_count += 1
def add_warning(self, message: str):
"""Add warning message and increment counter."""
self.issues.append(f"WARNING: {message}")
self.warning_count += 1
def add_success(self, message: str):
"""Add success message and increment counter."""
self.issues.append(f"SUCCESS: {message}")
self.success_count += 1
def validate_env_file(self) -> Dict[str, str]:
"""Validate .env file configuration."""
env_vars = self.load_env_file()
# Check for required variables
missing_vars = []
for var in self.required_vars:
if var not in env_vars or not env_vars[var].strip():
missing_vars.append(var)
if missing_vars:
self.add_error(f"Missing required variables in .env file: {missing_vars}")
else:
self.add_success("All required variables present in .env file")
# Check backend setting
backend = env_vars.get('MCP_MEMORY_STORAGE_BACKEND', '').lower()
if backend == 'cloudflare':
self.add_success(".env file configured for Cloudflare backend")
elif backend:
self.add_warning(f".env file configured for '{backend}' backend (not Cloudflare)")
else:
self.add_error("MCP_MEMORY_STORAGE_BACKEND not set in .env file")
return env_vars
def validate_claude_desktop_config(self) -> Optional[Dict[str, str]]:
"""Validate Claude Desktop configuration."""
config = self.load_json_safe(self.claude_desktop_config_file)
if not config:
self.add_error(f"Could not load Claude Desktop config from {self.claude_desktop_config_file}")
return None
# Extract memory server configuration
mcp_servers = config.get('mcpServers', {})
memory_server = mcp_servers.get('memory', {})
if not memory_server:
self.add_error("Memory server not found in Claude Desktop configuration")
return None
self.add_success("Memory server found in Claude Desktop configuration")
# Get environment variables from memory server config
memory_env = memory_server.get('env', {})
# Check required variables
missing_vars = []
for var in self.required_vars:
if var not in memory_env or not str(memory_env[var]).strip():
missing_vars.append(var)
if missing_vars:
self.add_error(f"Missing required variables in Claude Desktop config: {missing_vars}")
else:
self.add_success("All required variables present in Claude Desktop config")
return memory_env
def validate_claude_code_config(self):
"""Validate Claude Code global configuration (different from Claude Desktop)."""
config = self.load_json_safe(self.claude_code_config_file)
if not config:
self.add_warning(f"Claude Code config not found at {self.claude_code_config_file} (this is optional)")
return
# Check for memory server configurations in projects
memory_configs = []
projects = config.get('projects', {})
for project_path, project_config in projects.items():
mcp_servers = project_config.get('mcpServers', {})
if 'memory' in mcp_servers:
memory_config = mcp_servers['memory']
backend = memory_config.get('env', {}).get('MCP_MEMORY_STORAGE_BACKEND', 'unknown')
memory_configs.append((project_path, backend))
if memory_configs:
cloudflare_configs = [cfg for cfg in memory_configs if cfg[1] == 'cloudflare']
non_cloudflare_configs = [cfg for cfg in memory_configs if cfg[1] != 'cloudflare']
if cloudflare_configs:
self.add_success(f"Found {len(cloudflare_configs)} Cloudflare memory configurations in Claude Code")
if non_cloudflare_configs:
self.add_warning(f"Found {len(non_cloudflare_configs)} non-Cloudflare memory configurations in Claude Code")
else:
self.add_warning("No memory server configurations found in Claude Code (this is optional)")
def validate_local_mcp_config(self):
"""Check for conflicting local .mcp.json files."""
if self.local_mcp_config_file.exists():
config = self.load_json_safe(self.local_mcp_config_file)
if config and 'memory' in config.get('mcpServers', {}):
self.add_error("Local .mcp.json contains memory server configuration (conflicts with global config)")
else:
self.add_success("Local .mcp.json exists but does not conflict with memory configuration")
else:
self.add_success("No local .mcp.json found (using global configuration)")
def compare_configurations(self, env_config: Dict[str, str], claude_desktop_config: Optional[Dict[str, str]]):
"""Compare configurations between .env and Claude Desktop config."""
if not claude_desktop_config:
self.add_error("Cannot compare configurations - Claude Desktop config not available")
return
# Compare each required variable
differences = []
for var in self.required_vars:
env_value = env_config.get(var, '<MISSING>')
claude_value = str(claude_desktop_config.get(var, '<MISSING>'))
if env_value != claude_value:
differences.append((var, env_value, claude_value))
if differences:
self.add_warning(f"Found {len(differences)} configuration differences between .env and Claude Desktop config:")
for var, env_val, claude_val in differences:
self.add_warning(f" {var}: .env='{env_val[:50]}...' vs Claude='{claude_val[:50]}...'")
else:
self.add_success("All configurations match between .env and Claude Desktop config")
def validate_api_token_format(self, token: str) -> Tuple[bool, str]:
"""Validate API token format and detect known invalid tokens."""
if not token or token == '<MISSING>':
return False, "Token is missing"
if len(token) < 20:
return False, "Token appears too short"
if not any(c.isalnum() for c in token):
return False, "Token should contain alphanumeric characters"
# Check for known placeholder/invalid tokens
invalid_tokens = [
'your_token_here',
'replace_with_token',
'mkdXbb-iplcHNBRQ5tfqV3Sh_7eALYBpO4e3Di1m' # Known invalid token
]
if token in invalid_tokens:
return False, "Token appears to be a placeholder or known invalid token"
return True, "Token format appears valid"
def validate_api_tokens(self, env_config: Dict[str, str], claude_desktop_config: Optional[Dict[str, str]]):
"""Validate API tokens in both configurations."""
# Check .env token
env_token = env_config.get('CLOUDFLARE_API_TOKEN', '')
is_valid, message = self.validate_api_token_format(env_token)
if is_valid:
self.add_success(f".env API token format: {message}")
else:
self.add_error(f".env API token: {message}")
# Check Claude Desktop token
if claude_desktop_config:
claude_token = str(claude_desktop_config.get('CLOUDFLARE_API_TOKEN', ''))
is_valid, message = self.validate_api_token_format(claude_token)
if is_valid:
self.add_success(f"Claude Desktop API token format: {message}")
else:
self.add_error(f"Claude Desktop API token: {message}")
def check_environment_conflicts(self):
"""Check for conflicting environment configurations."""
# Check for conflicting .env files
env_files = list(self.project_root.glob('.env*'))
# Exclude legitimate backup files
conflicting_files = [
f for f in env_files
if f.name.endswith('.sqlite') and not f.name.endswith('.backup') and f.name != '.env.sqlite'
]
if conflicting_files:
self.add_warning(f"Potentially conflicting environment files found: {[str(f.name) for f in conflicting_files]}")
else:
self.add_success("No conflicting environment files detected")
def run_comprehensive_validation(self) -> bool:
"""Run complete configuration validation across all sources."""
print("Comprehensive MCP Memory Service Configuration Validation")
print("=" * 70)
# 1. Environment file validation
print("\n1. Environment File (.env) Validation:")
env_config = self.validate_env_file()
self._print_section_results()
# 2. Claude Desktop configuration validation
print("\n2. Claude Desktop Configuration Validation:")
claude_desktop_config = self.validate_claude_desktop_config()
self._print_section_results()
# 3. Claude Code configuration validation (optional)
print("\n3. Claude Code Global Configuration Check:")
self.validate_claude_code_config()
self._print_section_results()
# 4. Local MCP configuration check
print("\n4. Local Project Configuration Check:")
self.validate_local_mcp_config()
self._print_section_results()
# 5. Cross-configuration comparison
print("\n5. Cross-Configuration Consistency Check:")
self.compare_configurations(env_config, claude_desktop_config)
self._print_section_results()
# 6. API token validation
print("\n6. API Token Validation:")
self.validate_api_tokens(env_config, claude_desktop_config)
self._print_section_results()
# 7. Environment conflicts check
print("\n7. Environment Conflicts Check:")
self.check_environment_conflicts()
self._print_section_results()
# Final summary
self._print_final_summary()
return self.error_count == 0
def _print_section_results(self):
"""Print results for the current section."""
# Print only new issues since last call
current_total = len(self.issues)
if hasattr(self, '_last_printed_index'):
start_index = self._last_printed_index
else:
start_index = 0
for issue in self.issues[start_index:]:
print(f" {issue}")
self._last_printed_index = current_total
def _print_final_summary(self):
"""Print comprehensive final summary."""
print("\n" + "=" * 70)
print("VALIDATION SUMMARY")
print("=" * 70)
if self.error_count == 0:
print("CONFIGURATION VALIDATION PASSED!")
print(f" SUCCESS: {self.success_count} checks passed")
if self.warning_count > 0:
print(f" WARNING: {self.warning_count} warnings (non-critical)")
print("\nYour MCP Memory Service configuration appears to be correct.")
print("You should be able to use the memory service with Cloudflare backend.")
else:
print("CONFIGURATION VALIDATION FAILED!")
print(f" ERROR: {self.error_count} critical errors found")
print(f" WARNING: {self.warning_count} warnings")
print(f" SUCCESS: {self.success_count} checks passed")
print("\nPlease fix the critical errors above before using the memory service.")
print(f"\nConfiguration files checked:")
print(f" • .env file: {self.env_file}")
print(f" • Claude Desktop config: {self.claude_desktop_config_file}")
print(f" • Claude Code config: {self.claude_code_config_file}")
print(f" • Local MCP config: {self.local_mcp_config_file}")
def main():
"""Main validation function."""
validator = ComprehensiveConfigValidator()
success = validator.run_comprehensive_validation()
return 0 if success else 1
if __name__ == "__main__":
sys.exit(main())