configure.py•8.63 kB
#!/usr/bin/env python3
"""Configure Claude Desktop to use Obsidian MCP server."""
import json
import os
import sys
import platform
import argparse
import shutil
from pathlib import Path
from datetime import datetime
from typing import Dict, Any, Optional
class ConfigManager:
"""Manages Claude Desktop configuration for Obsidian MCP."""
def __init__(self):
self.config_path = self._get_config_path()
self.config: Dict[str, Any] = {}
def _get_config_path(self) -> Path:
"""Get Claude Desktop config path based on OS."""
system = platform.system()
if system == "Darwin": # macOS
config_dir = Path.home() / "Library" / "Application Support" / "Claude"
elif system == "Windows":
config_dir = Path(os.environ["APPDATA"]) / "Claude"
elif system == "Linux":
config_dir = Path.home() / ".config" / "Claude"
else:
raise ValueError(f"Unsupported operating system: {system}")
config_path = config_dir / "claude_desktop_config.json"
return config_path
def load_config(self) -> bool:
"""Load existing config or create empty one."""
if self.config_path.exists():
try:
with open(self.config_path, 'r') as f:
self.config = json.load(f)
return True
except json.JSONDecodeError:
print(f"⚠️ Warning: Invalid JSON in {self.config_path}")
self.config = {}
return False
else:
print(f"📝 Creating new config at {self.config_path}")
self.config = {"mcpServers": {}}
# Ensure directory exists
self.config_path.parent.mkdir(parents=True, exist_ok=True)
return False
def backup_config(self) -> Optional[Path]:
"""Create backup of existing config."""
if self.config_path.exists():
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
backup_path = self.config_path.with_name(
f"{self.config_path.stem}.backup_{timestamp}.json"
)
shutil.copy2(self.config_path, backup_path)
print(f"💾 Backup saved to: {backup_path.name}")
return backup_path
return None
def detect_old_config(self, server_name: str = "obsidian") -> Optional[Dict[str, Any]]:
"""Detect if old REST API configuration exists."""
if "mcpServers" not in self.config:
return None
servers = self.config["mcpServers"]
if server_name not in servers:
return None
old_config = servers[server_name]
# Check for indicators of old REST API config
is_old = any([
"cwd" in old_config,
old_config.get("args") == ["-m", "src.server"],
"OBSIDIAN_REST_API_KEY" in old_config.get("env", {}),
"PYTHONPATH" in old_config.get("env", {}),
old_config.get("command", "").endswith("python") or old_config.get("command", "").endswith("python3")
])
return old_config if is_old else None
def update_config(self, vault_path: str, server_name: str = "obsidian", force: bool = False) -> bool:
"""Update config with new Obsidian MCP settings."""
# Ensure mcpServers exists
if "mcpServers" not in self.config:
self.config["mcpServers"] = {}
# Check for existing config
old_config = self.detect_old_config(server_name)
if old_config:
print("\n🔄 Migration detected!")
print("Found old REST API configuration:")
print(f" - Command: {old_config.get('command', 'N/A')}")
print(f" - Working directory: {old_config.get('cwd', 'N/A')}")
print(" - Using REST API plugin")
print("\nMigrating to v2.0 direct filesystem access...")
# Try to extract vault path from old config if not provided
if not vault_path and "OBSIDIAN_VAULT_PATH" in old_config.get("env", {}):
vault_path = old_config["env"]["OBSIDIAN_VAULT_PATH"]
print(f" - Reusing vault path: {vault_path}")
elif server_name in self.config["mcpServers"] and not force:
print(f"\n⚠️ Server '{server_name}' already exists in config.")
response = input("Overwrite? (y/N): ").strip().lower()
if response != 'y':
print("❌ Configuration cancelled.")
return False
# Validate vault path
vault_path = Path(vault_path).expanduser().resolve()
if not vault_path.exists():
print(f"❌ Vault path does not exist: {vault_path}")
return False
if not vault_path.is_dir():
print(f"❌ Vault path is not a directory: {vault_path}")
return False
# Create new config
new_config = {
"command": "uvx",
"args": ["obsidian-mcp"],
"env": {
"OBSIDIAN_VAULT_PATH": str(vault_path)
}
}
# Update config
self.config["mcpServers"][server_name] = new_config
# Show what changed
if old_config:
print("\n✅ Migration complete!")
print(" - Removed dependency on Local REST API plugin")
print(" - Now using direct filesystem access (faster!)")
print(f" - Vault path: {vault_path}")
else:
print(f"\n✅ Added Obsidian MCP server '{server_name}'")
print(f" - Vault path: {vault_path}")
return True
def save_config(self) -> None:
"""Save config to file with pretty formatting."""
with open(self.config_path, 'w') as f:
json.dump(self.config, f, indent=2)
print(f"\n💾 Configuration saved to: {self.config_path}")
def show_usage(self) -> None:
"""Show how to use the configured server."""
print("\n🚀 Setup complete! Restart Claude Desktop to use Obsidian MCP.")
print("\nExample prompts to try:")
print(" - 'Show me all notes I modified this week'")
print(" - 'Create a new daily note for today'")
print(" - 'Search for all notes about project planning'")
def main():
"""Main entry point for configuration script."""
parser = argparse.ArgumentParser(
description="Configure Claude Desktop to use Obsidian MCP server",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
# Configure with your vault
obsidian-mcp-configure --vault-path /path/to/vault
# Use custom server name
obsidian-mcp-configure --vault-path /path/to/vault --name my-vault
# Force overwrite without prompting
obsidian-mcp-configure --vault-path /path/to/vault --force
"""
)
parser.add_argument(
"--vault-path",
type=str,
required=True,
help="Path to your Obsidian vault"
)
parser.add_argument(
"--name",
type=str,
default="obsidian",
help="Server name in Claude config (default: obsidian)"
)
parser.add_argument(
"--force",
action="store_true",
help="Force overwrite existing config without prompting"
)
parser.add_argument(
"--no-backup",
action="store_true",
help="Skip creating backup of existing config"
)
args = parser.parse_args()
# Header
print("🔮 Obsidian MCP Configuration Tool")
print("=" * 40)
try:
# Initialize config manager
manager = ConfigManager()
# Load existing config
config_exists = manager.load_config()
# Create backup if requested and config exists
if config_exists and not args.no_backup:
manager.backup_config()
# Update configuration
success = manager.update_config(
vault_path=args.vault_path,
server_name=args.name,
force=args.force
)
if success:
# Save config
manager.save_config()
# Show usage instructions
manager.show_usage()
else:
sys.exit(1)
except Exception as e:
print(f"\n❌ Error: {e}")
sys.exit(1)
if __name__ == "__main__":
main()