Skip to main content
Glama
registry.py10.1 kB
"""Plugin registry for managing plugin metadata and state.""" import json from pathlib import Path from typing import Any, Dict, List, Optional from dataclasses import asdict from .base import PluginInfo from ..utils.logging import get_logger logger = get_logger(__name__) class PluginRegistry: """Registry for plugin metadata and configuration.""" def __init__(self, registry_file: Optional[str | Path] = None) -> None: """Initialize plugin registry. Args: registry_file: Path to registry file (optional) """ self.registry_file = Path(registry_file) if registry_file else Path("plugins.json") self.plugins_info: Dict[str, Dict[str, Any]] = {} self.plugin_configs: Dict[str, Dict[str, Any]] = {} self._logger = get_logger(__name__) # Load existing registry self._load_registry() def _load_registry(self) -> None: """Load plugin registry from file.""" if self.registry_file.exists(): try: with open(self.registry_file, "r") as f: data = json.load(f) self.plugins_info = data.get("plugins", {}) self.plugin_configs = data.get("configs", {}) self._logger.info(f"Loaded plugin registry from {self.registry_file}") except Exception as e: self._logger.error(f"Failed to load plugin registry: {e}") self.plugins_info = {} self.plugin_configs = {} def _save_registry(self) -> None: """Save plugin registry to file.""" try: data = { "plugins": self.plugins_info, "configs": self.plugin_configs, } # Ensure directory exists self.registry_file.parent.mkdir(parents=True, exist_ok=True) with open(self.registry_file, "w") as f: json.dump(data, f, indent=2) self._logger.debug(f"Saved plugin registry to {self.registry_file}") except Exception as e: self._logger.error(f"Failed to save plugin registry: {e}") def register_plugin_info(self, info: PluginInfo) -> None: """Register plugin information. Args: info: Plugin information to register """ self.plugins_info[info.name] = asdict(info) self._save_registry() self._logger.debug(f"Registered plugin info: {info.name}") def unregister_plugin_info(self, plugin_name: str) -> None: """Unregister plugin information. Args: plugin_name: Name of plugin to unregister """ if plugin_name in self.plugins_info: del self.plugins_info[plugin_name] # Also remove config if plugin_name in self.plugin_configs: del self.plugin_configs[plugin_name] self._save_registry() self._logger.debug(f"Unregistered plugin info: {plugin_name}") def get_plugin_info(self, plugin_name: str) -> Optional[PluginInfo]: """Get plugin information by name. Args: plugin_name: Name of plugin Returns: Plugin information or None """ if plugin_name in self.plugins_info: data = self.plugins_info[plugin_name] return PluginInfo(**data) return None def list_registered_plugins(self) -> List[PluginInfo]: """List all registered plugins. Returns: List of plugin information """ return [PluginInfo(**data) for data in self.plugins_info.values()] def is_plugin_enabled(self, plugin_name: str) -> bool: """Check if plugin is enabled. Args: plugin_name: Name of plugin Returns: True if enabled, False otherwise """ info = self.get_plugin_info(plugin_name) return info.enabled if info else False def set_plugin_enabled(self, plugin_name: str, enabled: bool) -> None: """Set plugin enabled state. Args: plugin_name: Name of plugin enabled: Whether plugin should be enabled """ if plugin_name in self.plugins_info: self.plugins_info[plugin_name]["enabled"] = enabled self._save_registry() self._logger.info(f"Plugin {plugin_name} {'enabled' if enabled else 'disabled'}") def set_plugin_config(self, plugin_name: str, config: Dict[str, Any]) -> None: """Set plugin configuration. Args: plugin_name: Name of plugin config: Plugin configuration """ self.plugin_configs[plugin_name] = config self._save_registry() self._logger.debug(f"Updated config for plugin: {plugin_name}") def get_plugin_config(self, plugin_name: str) -> Dict[str, Any]: """Get plugin configuration. Args: plugin_name: Name of plugin Returns: Plugin configuration (empty dict if none) """ return self.plugin_configs.get(plugin_name, {}) def update_plugin_config(self, plugin_name: str, updates: Dict[str, Any]) -> None: """Update plugin configuration. Args: plugin_name: Name of plugin updates: Configuration updates """ if plugin_name not in self.plugin_configs: self.plugin_configs[plugin_name] = {} self.plugin_configs[plugin_name].update(updates) self._save_registry() self._logger.debug(f"Updated config for plugin: {plugin_name}") def get_plugins_by_dependency(self, dependency: str) -> List[str]: """Get plugins that depend on a specific plugin. Args: dependency: Plugin name that others depend on Returns: List of plugin names that depend on the specified plugin """ dependents = [] for plugin_name, info_data in self.plugins_info.items(): dependencies = info_data.get("dependencies", []) if dependency in dependencies: dependents.append(plugin_name) return dependents def validate_dependencies(self) -> Dict[str, List[str]]: """Validate plugin dependencies. Returns: Dictionary mapping plugin names to list of missing dependencies """ missing_deps = {} registered_plugins = set(self.plugins_info.keys()) for plugin_name, info_data in self.plugins_info.items(): dependencies = info_data.get("dependencies", []) missing = [dep for dep in dependencies if dep not in registered_plugins] if missing: missing_deps[plugin_name] = missing return missing_deps def get_load_order(self) -> List[str]: """Get recommended plugin load order based on dependencies. Returns: List of plugin names in load order """ # Simple topological sort remaining = set(self.plugins_info.keys()) ordered = [] while remaining: # Find plugins with no unmet dependencies ready = [] for plugin_name in remaining: info_data = self.plugins_info[plugin_name] dependencies = set(info_data.get("dependencies", [])) # Check if all dependencies are already ordered if dependencies.issubset(set(ordered)): ready.append(plugin_name) if not ready: # Circular dependency or missing dependency # Add remaining plugins anyway (will fail at load time) ready = list(remaining) self._logger.warning("Circular or missing dependencies detected") # Add ready plugins to order for plugin_name in sorted(ready): # Sort for deterministic order ordered.append(plugin_name) remaining.remove(plugin_name) return ordered def export_registry(self, export_path: str | Path) -> None: """Export plugin registry to a different file. Args: export_path: Path to export file """ export_path = Path(export_path) try: data = { "plugins": self.plugins_info, "configs": self.plugin_configs, "metadata": { "exported_from": str(self.registry_file), "export_timestamp": str(Path().resolve()), }, } export_path.parent.mkdir(parents=True, exist_ok=True) with open(export_path, "w") as f: json.dump(data, f, indent=2) self._logger.info(f"Exported plugin registry to {export_path}") except Exception as e: self._logger.error(f"Failed to export plugin registry: {e}") raise def import_registry(self, import_path: str | Path, merge: bool = True) -> None: """Import plugin registry from a file. Args: import_path: Path to import file merge: Whether to merge with existing registry (True) or replace (False) """ import_path = Path(import_path) if not import_path.exists(): raise FileNotFoundError(f"Registry file not found: {import_path}") try: with open(import_path, "r") as f: data = json.load(f) imported_plugins = data.get("plugins", {}) imported_configs = data.get("configs", {}) if merge: self.plugins_info.update(imported_plugins) self.plugin_configs.update(imported_configs) else: self.plugins_info = imported_plugins self.plugin_configs = imported_configs self._save_registry() self._logger.info(f"Imported plugin registry from {import_path}") except Exception as e: self._logger.error(f"Failed to import plugin registry: {e}") raise

Latest Blog Posts

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/namnd00/mcp-server-hero'

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