# -*- coding: utf-8 -*-
"""Location: ./mcpgateway/plugins/framework/hook_registry.py
Copyright 2025
SPDX-License-Identifier: Apache-2.0
Authors: Teryl Taylor
Hook Registry.
This module provides a global registry for mapping hook types to their
corresponding payload and result Pydantic models. This enables external
plugins to properly serialize/deserialize payloads without needing direct
access to the specific plugin implementations.
"""
# Standard
from typing import Dict, Optional, Type, Union
# First-Party
from mcpgateway.plugins.framework.models import PluginPayload, PluginResult
class HookRegistry:
"""Global registry for hook type metadata.
This singleton registry maintains mappings between hook type names and their
associated Pydantic models for payloads and results. It enables dynamic
serialization/deserialization for external plugins.
Examples:
>>> from mcpgateway.plugins.framework import PluginPayload, PluginResult
>>> registry = HookRegistry()
>>> registry.register_hook("test_hook", PluginPayload, PluginResult)
>>> registry.get_payload_type("test_hook")
<class 'pydantic.main.BaseModel'>
>>> registry.get_result_type("test_hook")
<class 'mcpgateway.plugins.framework.models.PluginResult'>
"""
_instance: Optional["HookRegistry"] = None
_hook_payloads: Dict[str, Type[PluginPayload]] = {}
_hook_results: Dict[str, Type[PluginResult]] = {}
def __new__(cls) -> "HookRegistry":
"""Ensure singleton pattern for the registry.
Returns:
The singleton HookRegistry instance.
"""
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def register_hook(
self,
hook_type: str,
payload_class: Type[PluginPayload],
result_class: Type[PluginResult],
) -> None:
"""Register a hook type with its payload and result classes.
Args:
hook_type: The hook type identifier (e.g., "prompt_pre_fetch").
payload_class: The Pydantic model class for the hook's payload.
result_class: The Pydantic model class for the hook's result.
Examples:
>>> registry = HookRegistry()
>>> from mcpgateway.plugins.framework import PluginPayload, PluginResult
>>> registry.register_hook("custom_hook", PluginPayload, PluginResult)
"""
self._hook_payloads[hook_type] = payload_class
self._hook_results[hook_type] = result_class
def get_payload_type(self, hook_type: str) -> Optional[Type[PluginPayload]]:
"""Get the payload class for a hook type.
Args:
hook_type: The hook type identifier.
Returns:
The Pydantic payload class, or None if not registered.
Examples:
>>> registry = HookRegistry()
>>> registry.get_payload_type("unknown_hook")
"""
return self._hook_payloads.get(hook_type)
def get_result_type(self, hook_type: str) -> Optional[Type[PluginResult]]:
"""Get the result class for a hook type.
Args:
hook_type: The hook type identifier.
Returns:
The Pydantic result class, or None if not registered.
Examples:
>>> registry = HookRegistry()
>>> registry.get_result_type("unknown_hook")
"""
return self._hook_results.get(hook_type)
def json_to_payload(self, hook_type: str, payload: Union[str, dict]) -> PluginPayload:
"""Convert JSON to the appropriate payload Pydantic model.
Args:
hook_type: The hook type identifier.
payload: The payload as JSON string or dictionary.
Returns:
The deserialized Pydantic payload object.
Raises:
ValueError: If the hook type is not registered.
Examples:
>>> registry = HookRegistry()
>>> from mcpgateway.plugins.framework.hooks.prompts import PromptPrehookPayload, PromptPrehookResult
>>> registry.register_hook("test", PromptPrehookPayload, PromptPrehookResult)
>>> payload = registry.json_to_payload("test", {"prompt_id": "123"})
"""
payload_class = self.get_payload_type(hook_type)
if not payload_class:
raise ValueError(f"No payload type registered for hook: {hook_type}")
if isinstance(payload, str):
return payload_class.model_validate_json(payload)
return payload_class.model_validate(payload)
def json_to_result(self, hook_type: str, result: Union[str, dict]) -> PluginResult:
"""Convert JSON to the appropriate result Pydantic model.
Args:
hook_type: The hook type identifier.
result: The result as JSON string or dictionary.
Returns:
The deserialized Pydantic result object.
Raises:
ValueError: If the hook type is not registered.
Examples:
>>> registry = HookRegistry()
>>> from mcpgateway.plugins.framework import PluginPayload, PluginResult
>>> registry.register_hook("test", PluginPayload, PluginResult)
>>> result = registry.json_to_result("test", '{"continue_processing": true}')
"""
result_class = self.get_result_type(hook_type)
if not result_class:
raise ValueError(f"No result type registered for hook: {hook_type}")
if isinstance(result, str):
return result_class.model_validate_json(result)
return result_class.model_validate(result)
def is_registered(self, hook_type: str) -> bool:
"""Check if a hook type is registered.
Args:
hook_type: The hook type identifier.
Returns:
True if the hook is registered, False otherwise.
Examples:
>>> registry = HookRegistry()
>>> registry.is_registered("unknown")
False
"""
return hook_type in self._hook_payloads and hook_type in self._hook_results
def get_registered_hooks(self) -> list[str]:
"""Get all registered hook types.
Returns:
List of registered hook type identifiers.
Examples:
>>> registry = HookRegistry()
>>> hooks = registry.get_registered_hooks()
>>> isinstance(hooks, list)
True
"""
return list(self._hook_payloads.keys())
# Global singleton instance
_global_registry = HookRegistry()
def get_hook_registry() -> HookRegistry:
"""Get the global hook registry instance.
Returns:
The singleton HookRegistry instance.
Examples:
>>> registry = get_hook_registry()
>>> isinstance(registry, HookRegistry)
True
"""
return _global_registry