Community edition generated from private repo via sync pipeline. Includes 9 plugins (WordPress, WooCommerce, WP Advanced, Gitea, n8n, Supabase, OpenPanel, Appwrite, Directus) with ~587 tools. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
229 lines
6.7 KiB
Python
229 lines
6.7 KiB
Python
"""
|
|
Tool Registry - Central tool management for MCP
|
|
|
|
Manages tool definitions with type safety and validation.
|
|
Part of Option B clean architecture refactoring.
|
|
"""
|
|
|
|
import logging
|
|
from collections.abc import Callable
|
|
from typing import Any
|
|
|
|
from pydantic import BaseModel, ConfigDict, Field
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
class ToolDefinition(BaseModel):
|
|
"""
|
|
Type-safe tool definition.
|
|
|
|
Represents a single MCP tool with all necessary metadata.
|
|
|
|
Attributes:
|
|
name: Unique tool identifier (e.g., "wordpress_get_post")
|
|
description: Human-readable tool description
|
|
input_schema: JSON Schema for tool parameters
|
|
handler: Async function that executes the tool
|
|
required_scope: Required API key scope ("read", "write", "admin")
|
|
plugin_type: Plugin type this tool belongs to (e.g., "wordpress")
|
|
"""
|
|
|
|
name: str = Field(..., description="Unique tool identifier")
|
|
description: str = Field(..., description="Tool description")
|
|
input_schema: dict[str, Any] = Field(
|
|
default_factory=lambda: {"type": "object", "properties": {}},
|
|
description="JSON Schema for parameters",
|
|
)
|
|
handler: Callable = Field(..., description="Async handler function")
|
|
required_scope: str = Field(
|
|
default="read", description="Required API key scope (read/write/admin)"
|
|
)
|
|
plugin_type: str = Field(..., description="Plugin type (wordpress, gitea, etc)")
|
|
|
|
model_config = ConfigDict(arbitrary_types_allowed=True) # Allow Callable type
|
|
|
|
class ToolRegistry:
|
|
"""
|
|
Central registry for all MCP tools.
|
|
|
|
Manages tool registration, retrieval, and validation.
|
|
Ensures unique tool names and provides filtering by plugin type.
|
|
|
|
Examples:
|
|
>>> registry = ToolRegistry()
|
|
>>> tool = ToolDefinition(
|
|
... name="wordpress_list_posts",
|
|
... description="List WordPress posts",
|
|
... handler=async_handler_func,
|
|
... plugin_type="wordpress"
|
|
... )
|
|
>>> registry.register(tool)
|
|
>>> all_tools = registry.get_all()
|
|
>>> wp_tools = registry.get_by_plugin_type("wordpress")
|
|
"""
|
|
|
|
def __init__(self):
|
|
"""Initialize empty tool registry."""
|
|
self.tools: dict[str, ToolDefinition] = {}
|
|
self.logger = logging.getLogger("ToolRegistry")
|
|
self.logger.info("ToolRegistry initialized")
|
|
|
|
def register(self, tool: ToolDefinition) -> None:
|
|
"""
|
|
Register a tool in the registry.
|
|
|
|
Args:
|
|
tool: Tool definition to register
|
|
|
|
Raises:
|
|
ValueError: If tool name already exists
|
|
|
|
Examples:
|
|
>>> registry.register(tool_definition)
|
|
"""
|
|
if tool.name in self.tools:
|
|
raise ValueError(f"Tool '{tool.name}' already registered")
|
|
|
|
self.tools[tool.name] = tool
|
|
self.logger.debug(f"Registered tool: {tool.name} ({tool.plugin_type})")
|
|
|
|
def register_many(self, tools: list[ToolDefinition]) -> int:
|
|
"""
|
|
Register multiple tools at once.
|
|
|
|
Args:
|
|
tools: List of tool definitions
|
|
|
|
Returns:
|
|
Number of tools successfully registered
|
|
|
|
Examples:
|
|
>>> count = registry.register_many([tool1, tool2, tool3])
|
|
>>> print(f"Registered {count} tools")
|
|
"""
|
|
count = 0
|
|
for tool in tools:
|
|
try:
|
|
self.register(tool)
|
|
count += 1
|
|
except ValueError as e:
|
|
self.logger.warning(f"Skipped duplicate tool: {e}")
|
|
except Exception as e:
|
|
self.logger.error(f"Failed to register tool: {e}", exc_info=True)
|
|
|
|
self.logger.info(f"Registered {count}/{len(tools)} tools")
|
|
return count
|
|
|
|
def get_all(self) -> list[ToolDefinition]:
|
|
"""
|
|
Get all registered tools.
|
|
|
|
Returns:
|
|
List of all tool definitions
|
|
|
|
Examples:
|
|
>>> tools = registry.get_all()
|
|
>>> print(f"Total tools: {len(tools)}")
|
|
"""
|
|
return list(self.tools.values())
|
|
|
|
def get_by_name(self, name: str) -> ToolDefinition | None:
|
|
"""
|
|
Get a tool by its name.
|
|
|
|
Args:
|
|
name: Tool name
|
|
|
|
Returns:
|
|
Tool definition if found, None otherwise
|
|
|
|
Examples:
|
|
>>> tool = registry.get_by_name("wordpress_get_post")
|
|
>>> if tool:
|
|
... print(f"Found: {tool.description}")
|
|
"""
|
|
return self.tools.get(name)
|
|
|
|
def get_by_plugin_type(self, plugin_type: str) -> list[ToolDefinition]:
|
|
"""
|
|
Get all tools for a specific plugin type.
|
|
|
|
Args:
|
|
plugin_type: Plugin type (e.g., "wordpress", "gitea")
|
|
|
|
Returns:
|
|
List of tools for the specified plugin type
|
|
|
|
Examples:
|
|
>>> wp_tools = registry.get_by_plugin_type("wordpress")
|
|
>>> print(f"WordPress tools: {len(wp_tools)}")
|
|
"""
|
|
return [tool for tool in self.tools.values() if tool.plugin_type == plugin_type]
|
|
|
|
def get_count(self) -> int:
|
|
"""
|
|
Get total number of registered tools.
|
|
|
|
Returns:
|
|
Count of registered tools
|
|
|
|
Examples:
|
|
>>> count = registry.get_count()
|
|
"""
|
|
return len(self.tools)
|
|
|
|
def get_count_by_plugin(self) -> dict[str, int]:
|
|
"""
|
|
Get tool counts grouped by plugin type.
|
|
|
|
Returns:
|
|
Dictionary mapping plugin type to tool count
|
|
|
|
Examples:
|
|
>>> counts = registry.get_count_by_plugin()
|
|
>>> print(counts) # {'wordpress': 95, 'gitea': 50}
|
|
"""
|
|
counts = {}
|
|
for tool in self.tools.values():
|
|
plugin_type = tool.plugin_type
|
|
counts[plugin_type] = counts.get(plugin_type, 0) + 1
|
|
return counts
|
|
|
|
def clear(self) -> None:
|
|
"""
|
|
Clear all registered tools.
|
|
|
|
Primarily for testing purposes.
|
|
|
|
Examples:
|
|
>>> registry.clear()
|
|
>>> assert registry.get_count() == 0
|
|
"""
|
|
self.tools.clear()
|
|
self.logger.info("Tool registry cleared")
|
|
|
|
def __repr__(self) -> str:
|
|
"""String representation of registry."""
|
|
counts = self.get_count_by_plugin()
|
|
counts_str = ", ".join(f"{k}: {v}" for k, v in counts.items())
|
|
return f"ToolRegistry(total={self.get_count()}, {counts_str})"
|
|
|
|
# Singleton instance
|
|
_tool_registry: ToolRegistry | None = None
|
|
|
|
def get_tool_registry() -> ToolRegistry:
|
|
"""
|
|
Get the singleton tool registry instance.
|
|
|
|
Returns:
|
|
Global ToolRegistry instance
|
|
|
|
Examples:
|
|
>>> registry = get_tool_registry()
|
|
>>> registry.register(tool)
|
|
"""
|
|
global _tool_registry
|
|
if _tool_registry is None:
|
|
_tool_registry = ToolRegistry()
|
|
return _tool_registry
|