Skip to main content
Glama

Scribe MCP Server

by paxocial
scribe_cli.py17.1 kB
#!/usr/bin/env python3 """Scribe CLI utilities for repository management and diagnostics. This script provides command-line utilities for managing Scribe configuration and diagnosing issues with global Scribe deployment. """ import argparse import asyncio import sys from pathlib import Path from typing import Optional # Add the MCP_SPINE directory to the path sys.path.insert(0, str(Path(__file__).parent.parent)) from scribe_mcp.config.repo_config import RepoDiscovery, RepoConfig, get_current_repo_config, reload_repo_config from scribe_mcp.plugins.registry import initialize_plugins, get_plugin_registry, get_plugin_security_info from scribe_mcp.security.sandbox import get_safety_instance, check_permission from scribe_mcp.tools.rotate_log import verify_rotation_integrity, get_rotation_history from scribe_mcp.tools.manage_docs import manage_docs_main as manage_docs_entrypoint def init_repo(repo_path: Optional[Path] = None, force: bool = False) -> None: """Initialize Scribe configuration in a repository.""" if repo_path is None: repo_path = Path.cwd() try: repo_root = RepoDiscovery.find_repo_root(repo_path) if not repo_root: print(f"❌ Could not find repository root at {repo_path}") print(" 💡 Initialize a git repository or add a .scribe directory") sys.exit(1) config = RepoDiscovery.load_config(repo_root) # Check if already initialized scribe_config = repo_root / ".scribe" / "scribe.yaml" if scribe_config.exists() and not force: print(f"✅ Scribe is already initialized in {repo_root}") print(f" Config file: {scribe_config}") return # Ensure configuration exists RepoDiscovery.ensure_config(repo_root, config) # Create basic directory structure docs_dir = config.dev_plans_dir docs_dir.mkdir(parents=True, exist_ok=True) # Create .scribe directory structure scribe_dir = repo_root / ".scribe" plugins_dir = scribe_dir / "plugins" hooks_dir = scribe_dir / "hooks" templates_dir = scribe_dir / "templates" for directory in [plugins_dir, hooks_dir, templates_dir]: directory.mkdir(exist_ok=True) # Create example plugin example_plugin = plugins_dir / "example.py" if not example_plugin.exists(): example_plugin.write_text("""# Example Scribe Plugin from scribe_mcp.plugins.registry import TemplatePlugin, PolicyPlugin class ExampleTemplatePlugin(TemplatePlugin): name = "example-templates" version = "1.0.0" description = "Example custom templates" def initialize(self, config): self.config = config def get_template(self, template_type): if template_type == "custom_architecture": return "# Custom Architecture Template\\n\\nThis is a custom template." return None def list_templates(self): return ["custom_architecture"] """) # Create example hook example_hook = hooks_dir / "pre_append.py" if not example_hook.exists(): example_hook.write_text("""# Example pre-append hook def execute(entry_data): # Modify entry data before it's appended print(f"Hook: About to append entry: {entry_data.get('message', '')}") return entry_data """) # Update .gitignore gitignore = repo_root / ".gitignore" gitignore_lines = [] if gitignore.exists(): gitignore_lines = gitignore.read_text().splitlines() scribe_ignores = [ "# Scribe ignores", ".scribe/journals/", "*.db", "*.db-journal", ".scribe/cache/", ] for ignore in scribe_ignores: if ignore not in gitignore_lines: gitignore_lines.append(ignore) gitignore.write_text("\n".join(gitignore_lines) + "\n") print(f"✅ Initialized Scribe in {repo_root}") print(f" 📁 Config: {scribe_config}") print(f" 📁 Docs: {docs_dir}") print(f" 🔌 Plugins: {plugins_dir}") print(f" 🪝 Hooks: {hooks_dir}") print(f" 📋 Templates: {templates_dir}") print(" 📝 Updated .gitignore") except Exception as e: print(f"❌ Failed to initialize Scribe: {e}") sys.exit(1) def doctor(repo_path: Optional[Path] = None) -> None: """Run diagnostics on Scribe setup and configuration.""" print("\n🔍 Scribe Doctor - Diagnosing your setup...\n") try: # Test repository discovery print("1. Repository Discovery:") repo_root = RepoDiscovery.find_repo_root(repo_path) if repo_root: print(f" ✅ Found repository root: {repo_root}") else: print(f" ❌ Could not find repository root from {repo_path or Path.cwd()}") return # Test configuration loading print("\n2. Configuration:") config = RepoDiscovery.load_config(repo_root) print(f" ✅ Loaded configuration for repo: {config.repo_slug}") print(f" 📁 Dev plans directory: {config.dev_plans_dir}") print(f" 📄 Progress log name: {config.progress_log_name}") print(f" 🔌 Storage backend: {config.storage_backend}") # Test directory structure print("\n3. Directory Structure:") required_dirs = [config.dev_plans_dir] if config.plugins_dir: required_dirs.append(config.plugins_dir) for directory in required_dirs: if directory.exists(): print(f" ✅ {directory}") else: print(f" ⚠️ {directory} (will be created on first use)") # Test permissions print("\n4. Permissions:") safety = get_safety_instance() sandbox = safety.get_sandbox(repo_root) permission_checker = safety.get_permission_checker(repo_root) test_operations = ["read", "append", "rotate", "generate_docs"] for operation in test_operations: allowed = permission_checker.check_permission(operation) status = "✅" if allowed else "❌" print(f" {status} {operation}") # Test plugin system print("\n5. Plugin System:") try: initialize_plugins(config) registry = get_plugin_registry() print(f" ✅ Plugin registry initialized") print(f" 🔌 Loaded {len(registry.plugins)} plugin(s)") for plugin_name, plugin in registry.plugins.items(): print(f" - {plugin_name} v{plugin.version}: {plugin.description}") except Exception as e: print(f" ⚠️ Plugin system: {e}") # Test database connectivity (if applicable) print("\n6. Storage Backend:") try: if config.storage_backend == "sqlite": db_path = config.db_path or (repo_root / ".scribe" / "scribe.db") print(f" 📄 SQLite database: {db_path}") if db_path.exists(): print(f" ✅ Database file exists") else: print(f" ℹ️ Database will be created on first use") elif config.storage_backend == "postgres": print(f" 🐘 PostgreSQL backend configured") print(f" ℹ️ Database connectivity will be tested on server startup") except Exception as e: print(f" ⚠️ Storage backend: {e}") # Test file system permissions print("\n7. File System Permissions:") test_file = config.dev_plans_dir / ".scribe_write_test" try: test_file.write_text("test") test_file.unlink() print(f" ✅ Can write to documentation directory") except Exception as e: print(f" ❌ Cannot write to documentation directory: {e}") print("\n🎉 Diagnosis complete!") print("💡 If you see any warnings or errors, address them before using Scribe.") except Exception as e: print(f"❌ Doctor failed: {e}") sys.exit(1) def use_repo(repo_path: Path) -> None: """Switch to a different repository for Scribe operations.""" try: repo_root = RepoDiscovery.find_repo_root(repo_path) if not repo_root: print(f"❌ Could not find repository root at {repo_path}") sys.exit(1) # Reload configuration for the new repository global _current_repo_config _current_repo_config = None # Force cache invalidation repo_root, config = get_current_repo_config(refresh=True) print(f"🎯 Switched to repository: {config.repo_slug}") print(f" 📁 Root: {repo_root}") print(f" 📁 Docs: {config.dev_plans_dir}") # Test if we can access the repository try: safety = get_safety_instance() sandbox = safety.get_sandbox(repo_root) print(f" ✅ Repository is accessible") except Exception as e: print(f" ⚠️ Repository access issue: {e}") except Exception as e: print(f"❌ Failed to switch repository: {e}") sys.exit(1) def verify_logs_rotation(repo_path: Optional[Path] = None, project: Optional[str] = None, limit: int = 5) -> None: """Verify rotation integrity for logs.""" try: repo_root, config = get_current_repo_config(refresh=True) print(f"🔍 Verifying Log Rotation Integrity") print(f" Repository: {config.repo_slug}") print(f" Root: {repo_root}") # Get rotation history if project: history = get_rotation_history(project_name=project, limit=limit) else: # Get for current active project from scribe_mcp.tools.agent_project_utils import get_active_project active_project = get_active_project() if active_project: history = get_rotation_history(project_name=active_project.get("name"), limit=limit) else: history = [] if not history: print(" ℹ️ No rotation history found") return print(f" Rotation History:") for i, rotation in enumerate(history, 1): rotation_id = rotation.get("id", "unknown")[:8] timestamp = rotation.get("timestamp", "unknown") entries_rotated = rotation.get("entries_rotated", 0) archive_size = rotation.get("archive_size", 0) print(f" {i}. Rotation {rotation_id}") print(f" Timestamp: {timestamp}") print(f" Entries: {entries_rotated}") print(f" Archive Size: {archive_size} bytes") # Verify integrity for this rotation if "rotation_id" in rotation: try: verification = verify_rotation_integrity(rotation["rotation_id"]) if verification.get("valid", False): print(f" ✅ Integrity: VALID") else: print(f" ❌ Integrity: INVALID - {verification.get('error', 'Unknown error')}") except Exception as e: print(f" ⚠️ Integrity: ERROR - {e}") print(f" ✅ Rotation verification completed") except Exception as e: print(f"❌ Failed to verify rotation: {e}") import traceback traceback.print_exc() def manage_docs_cli(args) -> None: """CLI wrapper for manage_docs functionality.""" try: # Convert argparse namespace to dict and call manage_docs import sys sys.argv = ["manage-docs"] # Add command-line arguments if args.project: sys.argv.extend(["--project", args.project]) if args.doc: sys.argv.extend(["--doc", args.doc]) if args.action: sys.argv.extend(["--action", args.action]) if args.section: sys.argv.extend(["--section", args.section]) if args.content: sys.argv.extend(["--content", args.content]) if args.template: sys.argv.extend(["--template", args.template]) if args.dry_run: sys.argv.append("--dry-run") if args.metadata: for meta in args.metadata: sys.argv.extend(["--metadata", meta]) # Call the manage_docs main function manage_docs_entrypoint() except Exception as e: print(f"❌ Failed to manage docs: {e}") import traceback traceback.print_exc() def status(repo_path: Optional[Path] = None) -> None: """Show current Scribe status and configuration.""" try: repo_root, config = get_current_repo_config(refresh=True) print(f"📊 Scribe Status") print(f" Repository: {config.repo_slug}") print(f" Root: {repo_root}") print(f" Storage: {config.storage_backend}") if config.plugins_dir and config.plugins_dir.exists(): plugin_count = len(list(config.plugins_dir.glob("*.py"))) print(f" Plugins: {plugin_count} plugin(s)") # Show recent activity if possible progress_log = config.get_progress_log_path() if progress_log.exists(): lines = progress_log.read_text().splitlines() if lines: last_line = lines[-1] print(f" Last entry: {last_line[:100]}{'...' if len(last_line) > 100 else ''}") except Exception as e: print(f"❌ Failed to get status: {e}") sys.exit(1) def main() -> None: """Main CLI entry point.""" parser = argparse.ArgumentParser( description="Scribe CLI - Repository management and diagnostics", prog="scribe-cli" ) subparsers = parser.add_subparsers(dest="command", help="Available commands") # init command init_parser = subparsers.add_parser("init", help="Initialize Scribe in a repository") init_parser.add_argument("--path", type=Path, help="Repository path (default: current directory)") init_parser.add_argument("--force", action="store_true", help="Reinitialize even if already configured") # doctor command doctor_parser = subparsers.add_parser("doctor", help="Run diagnostics") doctor_parser.add_argument("--path", type=Path, help="Repository path to check (default: current directory)") # use command use_parser = subparsers.add_parser("use", help="Switch to a different repository") use_parser.add_argument("path", type=Path, help="Repository path to switch to") # status command status_parser = subparsers.add_parser("status", help="Show current Scribe status") status_parser.add_argument("--path", type=Path, help="Repository path (default: current directory)") # logs-verify command logs_verify_parser = subparsers.add_parser("logs-verify", help="Verify log rotation integrity") logs_verify_parser.add_argument("--project", help="Specific project to verify (default: active project)") logs_verify_parser.add_argument("--limit", type=int, default=5, help="Number of recent rotations to check (default: 5)") logs_verify_parser.add_argument("--path", type=Path, help="Repository path (default: current directory)") # manage-docs command manage_docs_parser = subparsers.add_parser("manage-docs", help="Manage project documentation") manage_docs_parser.add_argument("--project", help="Project name") manage_docs_parser.add_argument("--doc", required=True, choices=["architecture", "phase_plan", "checklist", "progress_log"], help="Document to manage") manage_docs_parser.add_argument("--action", required=True, choices=["replace_section", "append", "status_update"], help="Action to perform") manage_docs_parser.add_argument("--section", help="Section ID for replace_section/status_update actions") manage_docs_parser.add_argument("--content", help="Content for actions") manage_docs_parser.add_argument("--template", help="Template to use") manage_docs_parser.add_argument("--dry-run", action="store_true", help="Preview changes without applying") manage_docs_parser.add_argument("--metadata", action="append", help="Metadata key=value pairs (can be used multiple times)") manage_docs_parser.add_argument("--path", type=Path, help="Repository path (default: current directory)") args = parser.parse_args() if args.command == "init": init_repo(args.path, args.force) elif args.command == "doctor": doctor(args.path) elif args.command == "use": use_repo(args.path) elif args.command == "status": status(args.path) elif args.command == "logs-verify": verify_logs_rotation(args.path, args.project, args.limit) elif args.command == "manage-docs": manage_docs_cli(args) else: parser.print_help() if __name__ == "__main__": main()

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/paxocial/scribe_mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server