claude-mpm 3.9.7__py3-none-any.whl → 3.9.9__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/base_agent.json +1 -1
  3. claude_mpm/agents/templates/ticketing.json +1 -1
  4. claude_mpm/cli/__init__.py +3 -1
  5. claude_mpm/cli/commands/__init__.py +3 -1
  6. claude_mpm/cli/commands/cleanup.py +21 -1
  7. claude_mpm/cli/commands/mcp.py +821 -0
  8. claude_mpm/cli/parser.py +148 -1
  9. claude_mpm/config/memory_guardian_config.py +325 -0
  10. claude_mpm/constants.py +13 -0
  11. claude_mpm/hooks/claude_hooks/hook_handler.py +76 -19
  12. claude_mpm/models/state_models.py +433 -0
  13. claude_mpm/services/__init__.py +28 -0
  14. claude_mpm/services/communication/__init__.py +2 -2
  15. claude_mpm/services/communication/socketio.py +18 -16
  16. claude_mpm/services/infrastructure/__init__.py +4 -1
  17. claude_mpm/services/infrastructure/logging.py +3 -3
  18. claude_mpm/services/infrastructure/memory_guardian.py +770 -0
  19. claude_mpm/services/mcp_gateway/__init__.py +138 -0
  20. claude_mpm/services/mcp_gateway/config/__init__.py +17 -0
  21. claude_mpm/services/mcp_gateway/config/config_loader.py +232 -0
  22. claude_mpm/services/mcp_gateway/config/config_schema.py +234 -0
  23. claude_mpm/services/mcp_gateway/config/configuration.py +371 -0
  24. claude_mpm/services/mcp_gateway/core/__init__.py +51 -0
  25. claude_mpm/services/mcp_gateway/core/base.py +315 -0
  26. claude_mpm/services/mcp_gateway/core/exceptions.py +239 -0
  27. claude_mpm/services/mcp_gateway/core/interfaces.py +476 -0
  28. claude_mpm/services/mcp_gateway/main.py +326 -0
  29. claude_mpm/services/mcp_gateway/registry/__init__.py +12 -0
  30. claude_mpm/services/mcp_gateway/registry/service_registry.py +397 -0
  31. claude_mpm/services/mcp_gateway/registry/tool_registry.py +477 -0
  32. claude_mpm/services/mcp_gateway/server/__init__.py +15 -0
  33. claude_mpm/services/mcp_gateway/server/mcp_server.py +430 -0
  34. claude_mpm/services/mcp_gateway/server/mcp_server_simple.py +444 -0
  35. claude_mpm/services/mcp_gateway/server/stdio_handler.py +373 -0
  36. claude_mpm/services/mcp_gateway/tools/__init__.py +22 -0
  37. claude_mpm/services/mcp_gateway/tools/base_adapter.py +497 -0
  38. claude_mpm/services/mcp_gateway/tools/document_summarizer.py +729 -0
  39. claude_mpm/services/mcp_gateway/tools/hello_world.py +551 -0
  40. claude_mpm/utils/file_utils.py +293 -0
  41. claude_mpm/utils/platform_memory.py +524 -0
  42. claude_mpm/utils/subprocess_utils.py +305 -0
  43. {claude_mpm-3.9.7.dist-info → claude_mpm-3.9.9.dist-info}/METADATA +4 -1
  44. {claude_mpm-3.9.7.dist-info → claude_mpm-3.9.9.dist-info}/RECORD +49 -26
  45. claude_mpm/agents/templates/.claude-mpm/memories/README.md +0 -36
  46. claude_mpm/agents/templates/.claude-mpm/memories/engineer_agent.md +0 -39
  47. claude_mpm/agents/templates/.claude-mpm/memories/qa_agent.md +0 -38
  48. claude_mpm/agents/templates/.claude-mpm/memories/research_agent.md +0 -39
  49. claude_mpm/agents/templates/.claude-mpm/memories/version_control_agent.md +0 -38
  50. /claude_mpm/agents/templates/{research_memory_efficient.json → backup/research_memory_efficient.json} +0 -0
  51. {claude_mpm-3.9.7.dist-info → claude_mpm-3.9.9.dist-info}/WHEEL +0 -0
  52. {claude_mpm-3.9.7.dist-info → claude_mpm-3.9.9.dist-info}/entry_points.txt +0 -0
  53. {claude_mpm-3.9.7.dist-info → claude_mpm-3.9.9.dist-info}/licenses/LICENSE +0 -0
  54. {claude_mpm-3.9.7.dist-info → claude_mpm-3.9.9.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,138 @@
1
+ """
2
+ MCP Gateway Service Module
3
+ ==========================
4
+
5
+ This module provides the Model Context Protocol (MCP) gateway implementation for Claude MPM.
6
+ It enables integration with MCP-compatible tools and services through a standardized interface.
7
+
8
+ Part of ISS-0034: Infrastructure Setup - MCP Gateway Project Foundation
9
+
10
+ The MCP Gateway follows the claude-mpm service-oriented architecture with:
11
+ - Interface-based contracts for all components
12
+ - Dependency injection for service resolution
13
+ - Lazy loading for performance optimization
14
+ - Comprehensive error handling and logging
15
+
16
+ Structure:
17
+ - core/: Core interfaces and base classes for MCP services
18
+ - server/: MCP server implementation and lifecycle management
19
+ - tools/: Tool registry and tool adapter implementations
20
+ - config/: Configuration management for MCP Gateway
21
+ - registry/: Service discovery and registration
22
+ """
23
+
24
+ # Version information
25
+ __version__ = "0.1.0"
26
+
27
+ # Lazy imports to prevent circular dependencies and improve startup performance
28
+ def __getattr__(name):
29
+ """Lazy import mechanism for MCP Gateway components."""
30
+
31
+ # Core interfaces and base classes
32
+ if name == "IMCPServer":
33
+ from .core.interfaces import IMCPServer
34
+ return IMCPServer
35
+ elif name == "IMCPToolRegistry":
36
+ from .core.interfaces import IMCPToolRegistry
37
+ return IMCPToolRegistry
38
+ elif name == "IMCPConfiguration":
39
+ from .core.interfaces import IMCPConfiguration
40
+ return IMCPConfiguration
41
+ elif name == "IMCPToolAdapter":
42
+ from .core.interfaces import IMCPToolAdapter
43
+ return IMCPToolAdapter
44
+ elif name == "BaseMCPService":
45
+ from .core.base import BaseMCPService
46
+ return BaseMCPService
47
+
48
+ # Server implementations
49
+ elif name == "MCPServer":
50
+ from .server.mcp_server import MCPServer
51
+ return MCPServer
52
+ elif name == "StdioHandler":
53
+ from .server.stdio_handler import StdioHandler
54
+ return StdioHandler
55
+ elif name == "AlternativeStdioHandler":
56
+ from .server.stdio_handler import AlternativeStdioHandler
57
+ return AlternativeStdioHandler
58
+
59
+ # Tool registry and adapters
60
+ elif name == "ToolRegistry":
61
+ from .registry.tool_registry import ToolRegistry
62
+ return ToolRegistry
63
+ elif name == "BaseToolAdapter":
64
+ from .tools.base_adapter import BaseToolAdapter
65
+ return BaseToolAdapter
66
+ elif name == "EchoToolAdapter":
67
+ from .tools.base_adapter import EchoToolAdapter
68
+ return EchoToolAdapter
69
+ elif name == "CalculatorToolAdapter":
70
+ from .tools.base_adapter import CalculatorToolAdapter
71
+ return CalculatorToolAdapter
72
+ elif name == "SystemInfoToolAdapter":
73
+ from .tools.base_adapter import SystemInfoToolAdapter
74
+ return SystemInfoToolAdapter
75
+
76
+ # Configuration management
77
+ elif name == "MCPConfiguration":
78
+ from .config.configuration import MCPConfiguration
79
+ return MCPConfiguration
80
+ elif name == "MCPConfigLoader":
81
+ from .config.config_loader import MCPConfigLoader
82
+ return MCPConfigLoader
83
+
84
+ # Service registry
85
+ elif name == "MCPServiceRegistry":
86
+ from .registry.service_registry import MCPServiceRegistry
87
+ return MCPServiceRegistry
88
+
89
+ # Exceptions
90
+ elif name == "MCPException":
91
+ from .core.exceptions import MCPException
92
+ return MCPException
93
+ elif name == "MCPConfigurationError":
94
+ from .core.exceptions import MCPConfigurationError
95
+ return MCPConfigurationError
96
+ elif name == "MCPToolNotFoundError":
97
+ from .core.exceptions import MCPToolNotFoundError
98
+ return MCPToolNotFoundError
99
+ elif name == "MCPServerError":
100
+ from .core.exceptions import MCPServerError
101
+ return MCPServerError
102
+
103
+ raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
104
+
105
+ # Public API exports
106
+ __all__ = [
107
+ # Core interfaces
108
+ "IMCPServer",
109
+ "IMCPToolRegistry",
110
+ "IMCPConfiguration",
111
+ "IMCPToolAdapter",
112
+ "BaseMCPService",
113
+
114
+ # Server implementations
115
+ "MCPServer",
116
+ "StdioHandler",
117
+ "AlternativeStdioHandler",
118
+
119
+ # Tool management
120
+ "ToolRegistry",
121
+ "BaseToolAdapter",
122
+ "EchoToolAdapter",
123
+ "CalculatorToolAdapter",
124
+ "SystemInfoToolAdapter",
125
+
126
+ # Configuration
127
+ "MCPConfiguration",
128
+ "MCPConfigLoader",
129
+
130
+ # Service registry
131
+ "MCPServiceRegistry",
132
+
133
+ # Exceptions
134
+ "MCPException",
135
+ "MCPConfigurationError",
136
+ "MCPToolNotFoundError",
137
+ "MCPServerError",
138
+ ]
@@ -0,0 +1,17 @@
1
+ """
2
+ MCP Gateway Configuration Module
3
+ =================================
4
+
5
+ Configuration management for the MCP Gateway service.
6
+ """
7
+
8
+ from .configuration import MCPConfiguration
9
+ from .config_loader import MCPConfigLoader
10
+ from .config_schema import MCPConfigSchema, validate_config
11
+
12
+ __all__ = [
13
+ "MCPConfiguration",
14
+ "MCPConfigLoader",
15
+ "MCPConfigSchema",
16
+ "validate_config",
17
+ ]
@@ -0,0 +1,232 @@
1
+ """
2
+ MCP Gateway Configuration Loader
3
+ ================================
4
+
5
+ Handles loading and discovery of MCP configuration files.
6
+
7
+ Part of ISS-0034: Infrastructure Setup - MCP Gateway Project Foundation
8
+ """
9
+
10
+ import os
11
+ from pathlib import Path
12
+ from typing import Optional, List
13
+ import yaml
14
+
15
+ from claude_mpm.core.logger import get_logger
16
+
17
+
18
+ class MCPConfigLoader:
19
+ """
20
+ Configuration loader for MCP Gateway.
21
+
22
+ This class handles discovering and loading configuration files from
23
+ standard locations, supporting both user and system configurations.
24
+
25
+ WHY: We separate configuration loading from the main configuration
26
+ service to support multiple configuration sources and provide a clean
27
+ abstraction for configuration discovery.
28
+ """
29
+
30
+ # Standard configuration file search paths
31
+ CONFIG_SEARCH_PATHS = [
32
+ # User-specific configurations
33
+ Path("~/.claude/mcp/config.yaml"),
34
+ Path("~/.claude/mcp_gateway.yaml"),
35
+ Path("~/.config/claude-mpm/mcp_gateway.yaml"),
36
+
37
+ # Project-specific configurations
38
+ Path("./mcp_gateway.yaml"),
39
+ Path("./config/mcp_gateway.yaml"),
40
+ Path("./.claude/mcp_gateway.yaml"),
41
+
42
+ # System-wide configurations
43
+ Path("/etc/claude-mpm/mcp_gateway.yaml"),
44
+ ]
45
+
46
+ def __init__(self):
47
+ """Initialize configuration loader."""
48
+ self.logger = get_logger("MCPConfigLoader")
49
+
50
+ def find_config_file(self) -> Optional[Path]:
51
+ """
52
+ Find the first available configuration file.
53
+
54
+ Searches through standard locations and returns the first
55
+ existing configuration file.
56
+
57
+ Returns:
58
+ Path to configuration file if found, None otherwise
59
+ """
60
+ for config_path in self.CONFIG_SEARCH_PATHS:
61
+ expanded_path = config_path.expanduser()
62
+ if expanded_path.exists() and expanded_path.is_file():
63
+ self.logger.info(f"Found configuration file: {expanded_path}")
64
+ return expanded_path
65
+
66
+ self.logger.debug("No configuration file found in standard locations")
67
+ return None
68
+
69
+ def load_from_file(self, config_path: Path) -> Optional[dict]:
70
+ """
71
+ Load configuration from a specific file.
72
+
73
+ Args:
74
+ config_path: Path to configuration file
75
+
76
+ Returns:
77
+ Configuration dictionary if successful, None otherwise
78
+ """
79
+ try:
80
+ expanded_path = config_path.expanduser()
81
+
82
+ if not expanded_path.exists():
83
+ self.logger.error(f"Configuration file not found: {expanded_path}")
84
+ return None
85
+
86
+ with open(expanded_path, 'r') as f:
87
+ config = yaml.safe_load(f)
88
+
89
+ self.logger.info(f"Configuration loaded from {expanded_path}")
90
+ return config or {}
91
+
92
+ except yaml.YAMLError as e:
93
+ self.logger.error(f"Failed to parse YAML configuration: {e}")
94
+ return None
95
+ except Exception as e:
96
+ self.logger.error(f"Failed to load configuration: {e}")
97
+ return None
98
+
99
+ def load_from_env(self) -> dict:
100
+ """
101
+ Load configuration from environment variables.
102
+
103
+ Environment variables follow the pattern: MCP_GATEWAY_<SECTION>_<KEY>
104
+
105
+ Returns:
106
+ Configuration dictionary built from environment variables
107
+ """
108
+ config = {}
109
+ prefix = "MCP_GATEWAY_"
110
+
111
+ for env_key, env_value in os.environ.items():
112
+ if not env_key.startswith(prefix):
113
+ continue
114
+
115
+ # Parse environment variable into configuration path
116
+ config_path = env_key[len(prefix):].lower().split('_')
117
+
118
+ # Build nested configuration structure
119
+ current = config
120
+ for part in config_path[:-1]:
121
+ if part not in current:
122
+ current[part] = {}
123
+ current = current[part]
124
+
125
+ # Set the value
126
+ key = config_path[-1]
127
+ try:
128
+ # Try to parse as JSON for complex types
129
+ import json
130
+ current[key] = json.loads(env_value)
131
+ except:
132
+ # Fall back to string value
133
+ current[key] = env_value
134
+
135
+ self.logger.debug(f"Loaded from environment: {env_key}")
136
+
137
+ return config
138
+
139
+ def load(self, config_path: Optional[Path] = None) -> dict:
140
+ """
141
+ Load configuration from all sources.
142
+
143
+ Loads configuration in the following priority order:
144
+ 1. Default configuration
145
+ 2. File configuration (if found or specified)
146
+ 3. Environment variable overrides
147
+
148
+ Args:
149
+ config_path: Optional specific configuration file path
150
+
151
+ Returns:
152
+ Merged configuration dictionary
153
+ """
154
+ from .configuration import MCPConfiguration
155
+
156
+ # Start with defaults
157
+ config = MCPConfiguration.DEFAULT_CONFIG.copy()
158
+
159
+ # Load from file
160
+ file_path = config_path or self.find_config_file()
161
+ if file_path:
162
+ file_config = self.load_from_file(file_path)
163
+ if file_config:
164
+ config = self._merge_configs(config, file_config)
165
+
166
+ # Apply environment overrides
167
+ env_config = self.load_from_env()
168
+ if env_config:
169
+ config = self._merge_configs(config, env_config)
170
+
171
+ return config
172
+
173
+ def _merge_configs(self, base: dict, overlay: dict) -> dict:
174
+ """
175
+ Recursively merge two configuration dictionaries.
176
+
177
+ Args:
178
+ base: Base configuration
179
+ overlay: Configuration to merge in
180
+
181
+ Returns:
182
+ Merged configuration
183
+ """
184
+ result = base.copy()
185
+
186
+ for key, value in overlay.items():
187
+ if key in result and isinstance(result[key], dict) and isinstance(value, dict):
188
+ result[key] = self._merge_configs(result[key], value)
189
+ else:
190
+ result[key] = value
191
+
192
+ return result
193
+
194
+ def create_default_config(self, path: Path) -> bool:
195
+ """
196
+ Create a default configuration file.
197
+
198
+ Args:
199
+ path: Path where to create the configuration file
200
+
201
+ Returns:
202
+ True if file created successfully
203
+ """
204
+ from .configuration import MCPConfiguration
205
+
206
+ try:
207
+ expanded_path = path.expanduser()
208
+ expanded_path.parent.mkdir(parents=True, exist_ok=True)
209
+
210
+ with open(expanded_path, 'w') as f:
211
+ yaml.dump(
212
+ MCPConfiguration.DEFAULT_CONFIG,
213
+ f,
214
+ default_flow_style=False,
215
+ sort_keys=True
216
+ )
217
+
218
+ self.logger.info(f"Created default configuration at {expanded_path}")
219
+ return True
220
+
221
+ except Exception as e:
222
+ self.logger.error(f"Failed to create default configuration: {e}")
223
+ return False
224
+
225
+ def list_config_locations(self) -> List[str]:
226
+ """
227
+ List all configuration file search locations.
228
+
229
+ Returns:
230
+ List of configuration file paths (as strings)
231
+ """
232
+ return [str(path.expanduser()) for path in self.CONFIG_SEARCH_PATHS]
@@ -0,0 +1,234 @@
1
+ """
2
+ MCP Gateway Configuration Schema
3
+ ================================
4
+
5
+ Defines and validates the configuration schema for MCP Gateway.
6
+
7
+ Part of ISS-0034: Infrastructure Setup - MCP Gateway Project Foundation
8
+ """
9
+
10
+ from typing import Any, Dict, List, Optional
11
+ from dataclasses import dataclass
12
+
13
+
14
+ @dataclass
15
+ class MCPConfigSchema:
16
+ """
17
+ Configuration schema definition for MCP Gateway.
18
+
19
+ This class defines the structure and validation rules for
20
+ MCP Gateway configuration.
21
+ """
22
+
23
+ # Schema version for migration support
24
+ SCHEMA_VERSION = "1.0.0"
25
+
26
+ # Configuration schema definition
27
+ SCHEMA = {
28
+ "mcp": {
29
+ "type": "object",
30
+ "required": True,
31
+ "properties": {
32
+ "server": {
33
+ "type": "object",
34
+ "required": True,
35
+ "properties": {
36
+ "name": {"type": "string", "required": True},
37
+ "version": {"type": "string", "required": True},
38
+ "description": {"type": "string", "required": False},
39
+ "communication": {
40
+ "type": "object",
41
+ "required": True,
42
+ "properties": {
43
+ "type": {
44
+ "type": "string",
45
+ "required": True,
46
+ "enum": ["stdio", "websocket", "http"]
47
+ },
48
+ "timeout": {
49
+ "type": "number",
50
+ "required": False,
51
+ "min": 1,
52
+ "max": 3600
53
+ },
54
+ "buffer_size": {
55
+ "type": "integer",
56
+ "required": False,
57
+ "min": 1024,
58
+ "max": 1048576
59
+ },
60
+ }
61
+ },
62
+ "capabilities": {
63
+ "type": "object",
64
+ "required": False,
65
+ "properties": {
66
+ "tools": {"type": "boolean", "required": False},
67
+ "resources": {"type": "boolean", "required": False},
68
+ "prompts": {"type": "boolean", "required": False},
69
+ }
70
+ },
71
+ }
72
+ },
73
+ "tools": {
74
+ "type": "object",
75
+ "required": False,
76
+ "properties": {
77
+ "enabled": {"type": "boolean", "required": False},
78
+ "auto_discover": {"type": "boolean", "required": False},
79
+ "discovery_paths": {
80
+ "type": "array",
81
+ "required": False,
82
+ "items": {"type": "string"}
83
+ },
84
+ "timeout_default": {
85
+ "type": "number",
86
+ "required": False,
87
+ "min": 1,
88
+ "max": 300
89
+ },
90
+ "max_concurrent": {
91
+ "type": "integer",
92
+ "required": False,
93
+ "min": 1,
94
+ "max": 100
95
+ },
96
+ }
97
+ },
98
+ "logging": {
99
+ "type": "object",
100
+ "required": False,
101
+ "properties": {
102
+ "level": {
103
+ "type": "string",
104
+ "required": False,
105
+ "enum": ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
106
+ },
107
+ "file": {"type": "string", "required": False},
108
+ "max_size": {"type": "string", "required": False},
109
+ "max_files": {
110
+ "type": "integer",
111
+ "required": False,
112
+ "min": 1,
113
+ "max": 100
114
+ },
115
+ "format": {
116
+ "type": "string",
117
+ "required": False,
118
+ "enum": ["json", "text"]
119
+ },
120
+ }
121
+ },
122
+ "security": {
123
+ "type": "object",
124
+ "required": False,
125
+ "properties": {
126
+ "validate_schemas": {"type": "boolean", "required": False},
127
+ "sanitize_inputs": {"type": "boolean", "required": False},
128
+ "max_request_size": {
129
+ "type": "integer",
130
+ "required": False,
131
+ "min": 1024,
132
+ "max": 104857600 # 100MB max
133
+ },
134
+ "allowed_tools": {
135
+ "type": "array",
136
+ "required": False,
137
+ "items": {"type": "string"}
138
+ },
139
+ "blocked_tools": {
140
+ "type": "array",
141
+ "required": False,
142
+ "items": {"type": "string"}
143
+ },
144
+ }
145
+ },
146
+ }
147
+ }
148
+ }
149
+
150
+
151
+ def validate_config(config: Dict[str, Any], schema: Optional[Dict[str, Any]] = None) -> List[str]:
152
+ """
153
+ Validate configuration against schema.
154
+
155
+ Args:
156
+ config: Configuration dictionary to validate
157
+ schema: Schema to validate against (uses default if not provided)
158
+
159
+ Returns:
160
+ List of validation errors (empty if valid)
161
+ """
162
+ if schema is None:
163
+ schema = MCPConfigSchema.SCHEMA
164
+
165
+ errors = []
166
+
167
+ def validate_value(value: Any, spec: Dict[str, Any], path: str) -> None:
168
+ """Recursively validate a value against its specification."""
169
+
170
+ # Check type
171
+ expected_type = spec.get("type")
172
+ if expected_type:
173
+ if expected_type == "object" and not isinstance(value, dict):
174
+ errors.append(f"{path}: Expected object, got {type(value).__name__}")
175
+ return
176
+ elif expected_type == "array" and not isinstance(value, list):
177
+ errors.append(f"{path}: Expected array, got {type(value).__name__}")
178
+ return
179
+ elif expected_type == "string" and not isinstance(value, str):
180
+ errors.append(f"{path}: Expected string, got {type(value).__name__}")
181
+ return
182
+ elif expected_type == "number" and not isinstance(value, (int, float)):
183
+ errors.append(f"{path}: Expected number, got {type(value).__name__}")
184
+ return
185
+ elif expected_type == "integer" and not isinstance(value, int):
186
+ errors.append(f"{path}: Expected integer, got {type(value).__name__}")
187
+ return
188
+ elif expected_type == "boolean" and not isinstance(value, bool):
189
+ errors.append(f"{path}: Expected boolean, got {type(value).__name__}")
190
+ return
191
+
192
+ # Check enum values
193
+ if "enum" in spec and value not in spec["enum"]:
194
+ errors.append(f"{path}: Value '{value}' not in allowed values: {spec['enum']}")
195
+
196
+ # Check numeric constraints
197
+ if isinstance(value, (int, float)):
198
+ if "min" in spec and value < spec["min"]:
199
+ errors.append(f"{path}: Value {value} is less than minimum {spec['min']}")
200
+ if "max" in spec and value > spec["max"]:
201
+ errors.append(f"{path}: Value {value} is greater than maximum {spec['max']}")
202
+
203
+ # Validate object properties
204
+ if expected_type == "object" and isinstance(value, dict):
205
+ properties = spec.get("properties", {})
206
+ for prop_name, prop_spec in properties.items():
207
+ prop_path = f"{path}.{prop_name}"
208
+
209
+ if prop_name in value:
210
+ validate_value(value[prop_name], prop_spec, prop_path)
211
+ elif prop_spec.get("required", False):
212
+ errors.append(f"{prop_path}: Required field missing")
213
+
214
+ # Validate array items
215
+ if expected_type == "array" and isinstance(value, list):
216
+ item_spec = spec.get("items", {})
217
+ for i, item in enumerate(value):
218
+ validate_value(item, item_spec, f"{path}[{i}]")
219
+
220
+ # Start validation from root
221
+ validate_value(config, {"type": "object", "properties": schema}, "config")
222
+
223
+ return errors
224
+
225
+
226
+ def generate_config_template() -> Dict[str, Any]:
227
+ """
228
+ Generate a configuration template with all possible options.
229
+
230
+ Returns:
231
+ Configuration template dictionary
232
+ """
233
+ from ..config.configuration import MCPConfiguration
234
+ return MCPConfiguration.DEFAULT_CONFIG