mcpmark / src /factory.py
haochengsama's picture
Add files using upload-large-folder tool
97cb846 verified
Raw
History Blame Contribute Delete
7.39 kB
#!/usr/bin/env python3
"""
MCP Service Factory for MCPMark
=================================
This module provides a simplified factory pattern for creating service-specific managers
with centralized configuration management.
Features:
- Dynamic service loading from definitions
- Centralized configuration
- Simplified service registration
"""
import importlib
from dataclasses import dataclass
from typing import Dict, Type
from src.base.login_helper import BaseLoginHelper
from src.base.state_manager import BaseStateManager
from src.base.task_manager import BaseTaskManager
from src.config.config_schema import ConfigRegistry
from src.services import get_service_definition, get_supported_mcp_services
@dataclass
class ServiceComponents:
"""All components required for an MCP service."""
task_manager_class: Type[BaseTaskManager]
state_manager_class: Type[BaseStateManager]
login_helper_class: Type[BaseLoginHelper]
config_mapping: Dict[str, Dict[str, str]]
def import_class(module_path: str):
"""Dynamically import a class from module path string."""
if not module_path:
return None
module_name, class_name = module_path.rsplit(".", 1)
module = importlib.import_module(module_name)
return getattr(module, class_name)
def apply_config_mapping(config: dict, mapping: dict) -> dict:
"""Apply config mapping to transform config keys to constructor params."""
if not mapping:
return {}
result = {}
for param_name, config_key in mapping.items():
if config_key in config:
result[param_name] = config[config_key]
return result
class ServiceRegistry:
"""Central registry that loads MCP services from definitions."""
# Cache for loaded components
_components_cache: Dict[str, ServiceComponents] = {}
@classmethod
def get_components(cls, service_name: str) -> ServiceComponents:
"""Get MCP service components from definition."""
if service_name in cls._components_cache:
return cls._components_cache[service_name]
definition = get_service_definition(service_name)
# Import classes dynamically
components = ServiceComponents(
task_manager_class=import_class(definition["components"]["task_manager"]),
state_manager_class=import_class(definition["components"]["state_manager"]),
login_helper_class=import_class(definition["components"]["login_helper"]),
config_mapping=definition.get("config_mapping", {}),
)
cls._components_cache[service_name] = components
return components
class GenericServiceFactory:
"""Generic factory that works with any MCP service."""
def __init__(self, components: ServiceComponents, service_name: str):
self.components = components
self.service_name = service_name
def create_task_manager(self, **kwargs) -> BaseTaskManager:
"""Create task manager instance."""
return self.components.task_manager_class(**kwargs)
def create_state_manager(self, config) -> BaseStateManager:
"""Create state manager with config mapping."""
mapping = self.components.config_mapping.get("state_manager", {})
# Handle both dict and config schema objects
config_dict = config.get_all() if hasattr(config, "get_all") else config
kwargs = apply_config_mapping(config_dict, mapping)
return self.components.state_manager_class(**kwargs)
def create_login_helper(self, config) -> BaseLoginHelper:
"""Create login helper with config mapping."""
mapping = self.components.config_mapping.get("login_helper", {})
# Handle both dict and config schema objects
config_dict = config.get_all() if hasattr(config, "get_all") else config
kwargs = apply_config_mapping(config_dict, mapping)
# Special handling for GitHub login helper - it needs a single token
if self.service_name == "github" and "token" in kwargs:
tokens_list = kwargs["token"]
if isinstance(tokens_list, list) and tokens_list:
kwargs["token"] = tokens_list[0] # Use first token for login helper
return self.components.login_helper_class(**kwargs)
class MCPServiceFactory:
"""Main factory interface."""
@classmethod
def create_service_config(cls, service_name: str):
"""Create MCP service configuration (backward compatible)."""
config = ConfigRegistry.get_config(service_name)
# Create a backward-compatible ServiceConfig-like object
class ServiceConfigCompat:
def __init__(self, service_name: str, config_dict: dict):
self.service_name = service_name
self.config = config_dict
self.api_key = config_dict.get("api_key")
return ServiceConfigCompat(service_name, config.get_all())
@classmethod
def create_task_manager(cls, service_name: str, **kwargs) -> BaseTaskManager:
"""Create task manager for the specified MCP service."""
components = ServiceRegistry.get_components(service_name)
return components.task_manager_class(**kwargs)
@classmethod
def create_state_manager(cls, service_name: str, **kwargs) -> BaseStateManager:
"""Create state manager for the specified MCP service."""
components = ServiceRegistry.get_components(service_name)
config = ConfigRegistry.get_config(service_name).get_all()
# Use provided kwargs or apply config mapping
if not kwargs:
mapping = components.config_mapping.get("state_manager", {})
kwargs = apply_config_mapping(config, mapping)
return components.state_manager_class(**kwargs)
@classmethod
def create_login_helper(cls, service_name: str, **kwargs) -> BaseLoginHelper:
"""Create login helper for the specified MCP service."""
components = ServiceRegistry.get_components(service_name)
config = ConfigRegistry.get_config(service_name).get_all()
# Use provided kwargs or apply config mapping
if not kwargs:
mapping = components.config_mapping.get("login_helper", {})
kwargs = apply_config_mapping(config, mapping)
# Special handling for GitHub login helper - it needs a single token
if service_name == "github" and "token" in kwargs:
tokens_list = kwargs["token"]
if isinstance(tokens_list, list) and tokens_list:
kwargs["token"] = tokens_list[0] # Use first token for login helper
return components.login_helper_class(**kwargs)
@classmethod
def get_supported_mcp_services(cls) -> list:
"""Get list of supported MCP services."""
return get_supported_mcp_services()
@classmethod
def get_config_info(cls, service_name: str) -> dict:
"""Get detailed configuration information for debugging."""
config = ConfigRegistry.get_config(service_name)
return config.get_debug_info()
@classmethod
def export_config_template(cls, service_name: str, output_path: str) -> None:
"""Export a configuration template for an MCP service."""
from pathlib import Path
ConfigRegistry.export_template(service_name, Path(output_path))