mcp-proxy-adapter 6.9.27__py3-none-any.whl → 6.9.29__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.
Potentially problematic release.
This version of mcp-proxy-adapter might be problematic. Click here for more details.
- mcp_proxy_adapter/__init__.py +10 -0
- mcp_proxy_adapter/__main__.py +8 -21
- mcp_proxy_adapter/api/app.py +10 -913
- mcp_proxy_adapter/api/core/__init__.py +18 -0
- mcp_proxy_adapter/api/core/app_factory.py +243 -0
- mcp_proxy_adapter/api/core/lifespan_manager.py +55 -0
- mcp_proxy_adapter/api/core/registration_manager.py +166 -0
- mcp_proxy_adapter/api/core/ssl_context_factory.py +88 -0
- mcp_proxy_adapter/api/handlers.py +78 -199
- mcp_proxy_adapter/api/middleware/__init__.py +1 -44
- mcp_proxy_adapter/api/middleware/base.py +0 -42
- mcp_proxy_adapter/api/middleware/command_permission_middleware.py +0 -85
- mcp_proxy_adapter/api/middleware/error_handling.py +1 -127
- mcp_proxy_adapter/api/middleware/factory.py +0 -94
- mcp_proxy_adapter/api/middleware/logging.py +0 -112
- mcp_proxy_adapter/api/middleware/performance.py +0 -35
- mcp_proxy_adapter/api/middleware/protocol_middleware.py +2 -98
- mcp_proxy_adapter/api/middleware/transport_middleware.py +0 -37
- mcp_proxy_adapter/api/middleware/unified_security.py +10 -10
- mcp_proxy_adapter/api/middleware/user_info_middleware.py +0 -118
- mcp_proxy_adapter/api/openapi/__init__.py +21 -0
- mcp_proxy_adapter/api/openapi/command_integration.py +105 -0
- mcp_proxy_adapter/api/openapi/openapi_generator.py +40 -0
- mcp_proxy_adapter/api/openapi/openapi_registry.py +62 -0
- mcp_proxy_adapter/api/openapi/schema_loader.py +116 -0
- mcp_proxy_adapter/api/schemas.py +0 -61
- mcp_proxy_adapter/api/tool_integration.py +0 -117
- mcp_proxy_adapter/api/tools.py +0 -46
- mcp_proxy_adapter/cli/__init__.py +12 -0
- mcp_proxy_adapter/cli/commands/__init__.py +15 -0
- mcp_proxy_adapter/cli/commands/client.py +100 -0
- mcp_proxy_adapter/cli/commands/config_generate.py +21 -0
- mcp_proxy_adapter/cli/commands/config_validate.py +36 -0
- mcp_proxy_adapter/cli/commands/generate.py +259 -0
- mcp_proxy_adapter/cli/commands/server.py +174 -0
- mcp_proxy_adapter/cli/commands/sets.py +128 -0
- mcp_proxy_adapter/cli/commands/testconfig.py +177 -0
- mcp_proxy_adapter/cli/examples/__init__.py +8 -0
- mcp_proxy_adapter/cli/examples/http_basic.py +82 -0
- mcp_proxy_adapter/cli/examples/https_token.py +96 -0
- mcp_proxy_adapter/cli/examples/mtls_roles.py +103 -0
- mcp_proxy_adapter/cli/main.py +63 -0
- mcp_proxy_adapter/cli/parser.py +324 -0
- mcp_proxy_adapter/cli/validators.py +231 -0
- mcp_proxy_adapter/client/jsonrpc_client.py +406 -0
- mcp_proxy_adapter/client/proxy.py +45 -0
- mcp_proxy_adapter/commands/__init__.py +44 -28
- mcp_proxy_adapter/commands/auth_validation_command.py +7 -344
- mcp_proxy_adapter/commands/base.py +19 -43
- mcp_proxy_adapter/commands/builtin_commands.py +0 -75
- mcp_proxy_adapter/commands/catalog/__init__.py +20 -0
- mcp_proxy_adapter/commands/catalog/catalog_loader.py +34 -0
- mcp_proxy_adapter/commands/catalog/catalog_manager.py +122 -0
- mcp_proxy_adapter/commands/catalog/catalog_syncer.py +149 -0
- mcp_proxy_adapter/commands/catalog/command_catalog.py +43 -0
- mcp_proxy_adapter/commands/catalog/dependency_manager.py +37 -0
- mcp_proxy_adapter/commands/catalog_manager.py +58 -928
- mcp_proxy_adapter/commands/cert_monitor_command.py +0 -88
- mcp_proxy_adapter/commands/certificate_management_command.py +0 -45
- mcp_proxy_adapter/commands/command_registry.py +172 -904
- mcp_proxy_adapter/commands/config_command.py +0 -28
- mcp_proxy_adapter/commands/dependency_container.py +1 -70
- mcp_proxy_adapter/commands/dependency_manager.py +0 -128
- mcp_proxy_adapter/commands/echo_command.py +0 -34
- mcp_proxy_adapter/commands/health_command.py +0 -3
- mcp_proxy_adapter/commands/help_command.py +0 -159
- mcp_proxy_adapter/commands/hooks.py +0 -137
- mcp_proxy_adapter/commands/key_management_command.py +0 -25
- mcp_proxy_adapter/commands/load_command.py +7 -78
- mcp_proxy_adapter/commands/plugins_command.py +0 -16
- mcp_proxy_adapter/commands/protocol_management_command.py +0 -28
- mcp_proxy_adapter/commands/proxy_registration_command.py +0 -88
- mcp_proxy_adapter/commands/queue_commands.py +750 -0
- mcp_proxy_adapter/commands/registration_status_command.py +0 -43
- mcp_proxy_adapter/commands/registry/__init__.py +18 -0
- mcp_proxy_adapter/commands/registry/command_info.py +103 -0
- mcp_proxy_adapter/commands/registry/command_loader.py +207 -0
- mcp_proxy_adapter/commands/registry/command_manager.py +119 -0
- mcp_proxy_adapter/commands/registry/command_registry.py +217 -0
- mcp_proxy_adapter/commands/reload_command.py +0 -80
- mcp_proxy_adapter/commands/result.py +25 -77
- mcp_proxy_adapter/commands/role_test_command.py +0 -44
- mcp_proxy_adapter/commands/roles_management_command.py +0 -199
- mcp_proxy_adapter/commands/security_command.py +0 -30
- mcp_proxy_adapter/commands/settings_command.py +0 -68
- mcp_proxy_adapter/commands/ssl_setup_command.py +0 -42
- mcp_proxy_adapter/commands/token_management_command.py +0 -1
- mcp_proxy_adapter/commands/transport_management_command.py +0 -20
- mcp_proxy_adapter/commands/unload_command.py +0 -71
- mcp_proxy_adapter/config.py +15 -626
- mcp_proxy_adapter/core/__init__.py +5 -39
- mcp_proxy_adapter/core/app_factory.py +14 -36
- mcp_proxy_adapter/core/app_runner.py +0 -27
- mcp_proxy_adapter/core/auth_validator.py +1 -93
- mcp_proxy_adapter/core/certificate/__init__.py +20 -0
- mcp_proxy_adapter/core/certificate/certificate_creator.py +371 -0
- mcp_proxy_adapter/core/certificate/certificate_extractor.py +183 -0
- mcp_proxy_adapter/core/certificate/certificate_utils.py +249 -0
- mcp_proxy_adapter/core/certificate/certificate_validator.py +110 -0
- mcp_proxy_adapter/core/certificate/ssl_context_manager.py +70 -0
- mcp_proxy_adapter/core/certificate_utils.py +64 -903
- mcp_proxy_adapter/core/client.py +0 -6
- mcp_proxy_adapter/core/client_manager.py +0 -19
- mcp_proxy_adapter/core/client_security.py +0 -2
- mcp_proxy_adapter/core/config/__init__.py +18 -0
- mcp_proxy_adapter/core/config/config.py +195 -0
- mcp_proxy_adapter/core/config/config_factory.py +22 -0
- mcp_proxy_adapter/core/config/config_loader.py +66 -0
- mcp_proxy_adapter/core/config/feature_manager.py +31 -0
- mcp_proxy_adapter/core/config/simple_config.py +112 -0
- mcp_proxy_adapter/core/config/simple_config_generator.py +50 -0
- mcp_proxy_adapter/core/config/simple_config_validator.py +96 -0
- mcp_proxy_adapter/core/config_converter.py +0 -186
- mcp_proxy_adapter/core/config_validator.py +96 -1238
- mcp_proxy_adapter/core/errors.py +7 -42
- mcp_proxy_adapter/core/job_manager.py +54 -0
- mcp_proxy_adapter/core/logging.py +2 -22
- mcp_proxy_adapter/core/mtls_asgi.py +0 -20
- mcp_proxy_adapter/core/mtls_asgi_app.py +0 -12
- mcp_proxy_adapter/core/mtls_proxy.py +0 -80
- mcp_proxy_adapter/core/mtls_server.py +3 -173
- mcp_proxy_adapter/core/protocol_manager.py +1 -191
- mcp_proxy_adapter/core/proxy/__init__.py +22 -0
- mcp_proxy_adapter/core/proxy/auth_manager.py +27 -0
- mcp_proxy_adapter/core/proxy/proxy_registration_manager.py +137 -0
- mcp_proxy_adapter/core/proxy/registration_client.py +60 -0
- mcp_proxy_adapter/core/proxy/ssl_manager.py +101 -0
- mcp_proxy_adapter/core/proxy_client.py +0 -1
- mcp_proxy_adapter/core/proxy_registration.py +36 -912
- mcp_proxy_adapter/core/role_utils.py +0 -308
- mcp_proxy_adapter/core/security_adapter.py +1 -36
- mcp_proxy_adapter/core/security_factory.py +1 -150
- mcp_proxy_adapter/core/security_integration.py +0 -33
- mcp_proxy_adapter/core/server_adapter.py +1 -40
- mcp_proxy_adapter/core/server_engine.py +2 -173
- mcp_proxy_adapter/core/settings.py +0 -127
- mcp_proxy_adapter/core/signal_handler.py +0 -65
- mcp_proxy_adapter/core/ssl_utils.py +19 -137
- mcp_proxy_adapter/core/transport_manager.py +0 -151
- mcp_proxy_adapter/core/unified_config_adapter.py +1 -193
- mcp_proxy_adapter/core/utils.py +1 -182
- mcp_proxy_adapter/core/validation/__init__.py +21 -0
- mcp_proxy_adapter/core/validation/config_validator.py +211 -0
- mcp_proxy_adapter/core/validation/file_validator.py +73 -0
- mcp_proxy_adapter/core/validation/protocol_validator.py +191 -0
- mcp_proxy_adapter/core/validation/security_validator.py +58 -0
- mcp_proxy_adapter/core/validation/validation_result.py +27 -0
- mcp_proxy_adapter/custom_openapi.py +33 -652
- mcp_proxy_adapter/examples/bugfix_certificate_config.py +0 -23
- mcp_proxy_adapter/examples/check_config.py +0 -2
- mcp_proxy_adapter/examples/client_usage_example.py +164 -0
- mcp_proxy_adapter/examples/config_builder.py +13 -2
- mcp_proxy_adapter/examples/config_cli.py +0 -1
- mcp_proxy_adapter/examples/create_test_configs.py +0 -46
- mcp_proxy_adapter/examples/debug_request_state.py +0 -1
- mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +0 -47
- mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +0 -45
- mcp_proxy_adapter/examples/full_application/commands/echo_command.py +0 -12
- mcp_proxy_adapter/examples/full_application/commands/help_command.py +0 -12
- mcp_proxy_adapter/examples/full_application/commands/list_command.py +0 -7
- mcp_proxy_adapter/examples/full_application/hooks/__init__.py +0 -2
- mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +0 -59
- mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +0 -54
- mcp_proxy_adapter/examples/full_application/main.py +186 -150
- mcp_proxy_adapter/examples/full_application/proxy_endpoints.py +0 -107
- mcp_proxy_adapter/examples/full_application/test_minimal_server.py +0 -24
- mcp_proxy_adapter/examples/full_application/test_server.py +0 -58
- mcp_proxy_adapter/examples/generate_config.py +65 -11
- mcp_proxy_adapter/examples/queue_demo_simple.py +632 -0
- mcp_proxy_adapter/examples/queue_integration_example.py +578 -0
- mcp_proxy_adapter/examples/queue_server_demo.py +82 -0
- mcp_proxy_adapter/examples/queue_server_example.py +85 -0
- mcp_proxy_adapter/examples/queue_server_simple.py +173 -0
- mcp_proxy_adapter/examples/required_certificates.py +0 -2
- mcp_proxy_adapter/examples/run_full_test_suite.py +0 -29
- mcp_proxy_adapter/examples/run_proxy_server.py +31 -71
- mcp_proxy_adapter/examples/run_security_tests_fixed.py +0 -27
- mcp_proxy_adapter/examples/security_test/__init__.py +18 -0
- mcp_proxy_adapter/examples/security_test/auth_manager.py +14 -0
- mcp_proxy_adapter/examples/security_test/ssl_context_manager.py +28 -0
- mcp_proxy_adapter/examples/security_test/test_client.py +159 -0
- mcp_proxy_adapter/examples/security_test/test_result.py +22 -0
- mcp_proxy_adapter/examples/security_test_client.py +24 -1075
- mcp_proxy_adapter/examples/setup/__init__.py +24 -0
- mcp_proxy_adapter/examples/setup/certificate_manager.py +215 -0
- mcp_proxy_adapter/examples/setup/config_generator.py +12 -0
- mcp_proxy_adapter/examples/setup/config_validator.py +118 -0
- mcp_proxy_adapter/examples/setup/environment_setup.py +62 -0
- mcp_proxy_adapter/examples/setup/test_files_generator.py +10 -0
- mcp_proxy_adapter/examples/setup/test_runner.py +89 -0
- mcp_proxy_adapter/examples/setup_test_environment.py +133 -1425
- mcp_proxy_adapter/examples/test_config.py +0 -3
- mcp_proxy_adapter/examples/test_config_builder.py +25 -405
- mcp_proxy_adapter/examples/test_examples.py +0 -1
- mcp_proxy_adapter/examples/test_framework_complete.py +0 -2
- mcp_proxy_adapter/examples/test_mcp_server.py +0 -1
- mcp_proxy_adapter/examples/test_protocol_examples.py +0 -1
- mcp_proxy_adapter/examples/universal_client.py +0 -6
- mcp_proxy_adapter/examples/update_config_certificates.py +0 -1
- mcp_proxy_adapter/examples/validate_generator_compatibility.py +0 -1
- mcp_proxy_adapter/examples/validate_generator_compatibility_simple.py +0 -187
- mcp_proxy_adapter/integrations/__init__.py +25 -0
- mcp_proxy_adapter/integrations/queuemgr_integration.py +462 -0
- mcp_proxy_adapter/main.py +70 -62
- mcp_proxy_adapter/openapi.py +0 -22
- mcp_proxy_adapter/version.py +1 -1
- {mcp_proxy_adapter-6.9.27.dist-info → mcp_proxy_adapter-6.9.29.dist-info}/METADATA +2 -1
- mcp_proxy_adapter-6.9.29.dist-info/RECORD +235 -0
- {mcp_proxy_adapter-6.9.27.dist-info → mcp_proxy_adapter-6.9.29.dist-info}/entry_points.txt +1 -1
- mcp_proxy_adapter-6.9.27.dist-info/RECORD +0 -149
- {mcp_proxy_adapter-6.9.27.dist-info → mcp_proxy_adapter-6.9.29.dist-info}/WHEEL +0 -0
- {mcp_proxy_adapter-6.9.27.dist-info → mcp_proxy_adapter-6.9.29.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Author: Vasiliy Zdanovskiy
|
|
3
|
+
email: vasilyvz@gmail.com
|
|
4
|
+
|
|
5
|
+
Main configuration validator for MCP Proxy Adapter.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import logging
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Dict, List, Any, Optional
|
|
12
|
+
|
|
13
|
+
from .file_validator import FileValidator
|
|
14
|
+
from .security_validator import SecurityValidator
|
|
15
|
+
from .protocol_validator import ProtocolValidator
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ConfigValidator:
|
|
21
|
+
"""
|
|
22
|
+
Comprehensive configuration validator for MCP Proxy Adapter.
|
|
23
|
+
|
|
24
|
+
Validates:
|
|
25
|
+
- Required sections and keys
|
|
26
|
+
- File existence for referenced files
|
|
27
|
+
- Feature flag dependencies
|
|
28
|
+
- Protocol-specific requirements
|
|
29
|
+
- Security configuration consistency
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(self, config_path: Optional[str] = None):
|
|
33
|
+
"""
|
|
34
|
+
Initialize configuration validator.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
config_path: Path to configuration file (optional)
|
|
38
|
+
"""
|
|
39
|
+
self.config_path = config_path
|
|
40
|
+
self.config_data: Dict[str, Any] = {}
|
|
41
|
+
self.validation_results: List[ValidationResult] = []
|
|
42
|
+
|
|
43
|
+
def load_config(self, config_path: Optional[str] = None) -> None:
|
|
44
|
+
"""
|
|
45
|
+
Load configuration from file.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
config_path: Path to configuration file
|
|
49
|
+
"""
|
|
50
|
+
if config_path:
|
|
51
|
+
self.config_path = config_path
|
|
52
|
+
|
|
53
|
+
if not self.config_path:
|
|
54
|
+
raise ValueError("No configuration path provided")
|
|
55
|
+
|
|
56
|
+
try:
|
|
57
|
+
with open(self.config_path, 'r', encoding='utf-8') as f:
|
|
58
|
+
self.config_data = json.load(f)
|
|
59
|
+
logger.info(f"Configuration loaded from {self.config_path}")
|
|
60
|
+
except FileNotFoundError:
|
|
61
|
+
raise FileNotFoundError(f"Configuration file not found: {self.config_path}")
|
|
62
|
+
except json.JSONDecodeError as e:
|
|
63
|
+
raise ValueError(f"Invalid JSON in configuration file: {e}")
|
|
64
|
+
except Exception as e:
|
|
65
|
+
raise RuntimeError(f"Error loading configuration: {e}")
|
|
66
|
+
|
|
67
|
+
def validate_config(self, config_data: Optional[Dict[str, Any]] = None) -> List[ValidationResult]:
|
|
68
|
+
"""
|
|
69
|
+
Validate configuration data.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
config_data: Configuration data to validate (optional)
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
List of validation results
|
|
76
|
+
"""
|
|
77
|
+
if config_data is not None:
|
|
78
|
+
self.config_data = config_data
|
|
79
|
+
|
|
80
|
+
if not self.config_data:
|
|
81
|
+
raise ValueError("No configuration data to validate")
|
|
82
|
+
|
|
83
|
+
self.validation_results = []
|
|
84
|
+
|
|
85
|
+
# Initialize validators
|
|
86
|
+
file_validator = FileValidator(self.config_data)
|
|
87
|
+
security_validator = SecurityValidator(self.config_data)
|
|
88
|
+
protocol_validator = ProtocolValidator(self.config_data)
|
|
89
|
+
|
|
90
|
+
# Run all validations
|
|
91
|
+
self.validation_results.extend(protocol_validator.validate_required_sections())
|
|
92
|
+
self.validation_results.extend(protocol_validator.validate_protocol_requirements())
|
|
93
|
+
self.validation_results.extend(file_validator.validate_file_existence())
|
|
94
|
+
self.validation_results.extend(security_validator.validate_security_consistency())
|
|
95
|
+
self.validation_results.extend(security_validator.validate_ssl_configuration())
|
|
96
|
+
self.validation_results.extend(security_validator.validate_roles_configuration())
|
|
97
|
+
self.validation_results.extend(security_validator.validate_proxy_registration())
|
|
98
|
+
|
|
99
|
+
# Additional validations
|
|
100
|
+
self._validate_unknown_fields()
|
|
101
|
+
self._validate_uuid_format()
|
|
102
|
+
|
|
103
|
+
return self.validation_results
|
|
104
|
+
|
|
105
|
+
def validate_all(self, config_data: Optional[Dict[str, Any]] = None) -> List[ValidationResult]:
|
|
106
|
+
"""
|
|
107
|
+
Validate all aspects of the configuration.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
config_data: Configuration data to validate (optional)
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
List of validation results
|
|
114
|
+
"""
|
|
115
|
+
return self.validate_config(config_data)
|
|
116
|
+
|
|
117
|
+
def _validate_unknown_fields(self) -> None:
|
|
118
|
+
"""Validate for unknown configuration fields."""
|
|
119
|
+
known_sections = {
|
|
120
|
+
"server", "protocols", "security", "ssl", "auth", "roles",
|
|
121
|
+
"logging", "commands", "proxy_registration", "transport"
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
for section in self.config_data.keys():
|
|
125
|
+
if section not in known_sections:
|
|
126
|
+
self.validation_results.append(ValidationResult(
|
|
127
|
+
level="warning",
|
|
128
|
+
message=f"Unknown configuration section: {section}",
|
|
129
|
+
section=section,
|
|
130
|
+
suggestion="Check if this section is needed or if it's a typo"
|
|
131
|
+
))
|
|
132
|
+
|
|
133
|
+
def _validate_uuid_format(self) -> None:
|
|
134
|
+
"""Validate UUID format in configuration."""
|
|
135
|
+
uuid_fields = ["server.server_id", "proxy_registration.server_id"]
|
|
136
|
+
|
|
137
|
+
for field in uuid_fields:
|
|
138
|
+
value = self._get_nested_value_safe(field)
|
|
139
|
+
if value and not self._is_valid_uuid4(str(value)):
|
|
140
|
+
self.validation_results.append(ValidationResult(
|
|
141
|
+
level="warning",
|
|
142
|
+
message=f"Invalid UUID format in {field}: {value}",
|
|
143
|
+
section=field.split(".")[0],
|
|
144
|
+
key=field.split(".")[1],
|
|
145
|
+
suggestion="Use a valid UUID4 format"
|
|
146
|
+
))
|
|
147
|
+
|
|
148
|
+
def _is_valid_uuid4(self, uuid_str: str) -> bool:
|
|
149
|
+
"""Check if string is a valid UUID4."""
|
|
150
|
+
import re
|
|
151
|
+
uuid_pattern = r'^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'
|
|
152
|
+
return bool(re.match(uuid_pattern, uuid_str, re.IGNORECASE))
|
|
153
|
+
|
|
154
|
+
def _get_nested_value_safe(self, key: str, default: Any = None) -> Any:
|
|
155
|
+
"""Safely get a nested value from configuration."""
|
|
156
|
+
keys = key.split('.')
|
|
157
|
+
value = self.config_data
|
|
158
|
+
|
|
159
|
+
for k in keys:
|
|
160
|
+
if isinstance(value, dict) and k in value:
|
|
161
|
+
value = value[k]
|
|
162
|
+
else:
|
|
163
|
+
return default
|
|
164
|
+
|
|
165
|
+
return value
|
|
166
|
+
|
|
167
|
+
def get_validation_summary(self) -> Dict[str, Any]:
|
|
168
|
+
"""
|
|
169
|
+
Get a summary of validation results.
|
|
170
|
+
|
|
171
|
+
Returns:
|
|
172
|
+
Dictionary with validation summary
|
|
173
|
+
"""
|
|
174
|
+
error_count = sum(1 for r in self.validation_results if r.level == "error")
|
|
175
|
+
warning_count = sum(1 for r in self.validation_results if r.level == "warning")
|
|
176
|
+
info_count = sum(1 for r in self.validation_results if r.level == "info")
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
"total_issues": len(self.validation_results),
|
|
180
|
+
"errors": error_count,
|
|
181
|
+
"warnings": warning_count,
|
|
182
|
+
"info": info_count,
|
|
183
|
+
"is_valid": error_count == 0
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
def print_validation_report(self) -> None:
|
|
187
|
+
"""Print a formatted validation report."""
|
|
188
|
+
summary = self.get_validation_summary()
|
|
189
|
+
|
|
190
|
+
print(f"\\n📋 Configuration Validation Report")
|
|
191
|
+
print(f"{'=' * 40}")
|
|
192
|
+
print(f"Total issues: {summary['total_issues']}")
|
|
193
|
+
print(f"Errors: {summary['errors']}")
|
|
194
|
+
print(f"Warnings: {summary['warnings']}")
|
|
195
|
+
print(f"Info: {summary['info']}")
|
|
196
|
+
print(f"Valid: {'✅ Yes' if summary['is_valid'] else '❌ No'}")
|
|
197
|
+
|
|
198
|
+
if self.validation_results:
|
|
199
|
+
print(f"\\n📝 Issues:")
|
|
200
|
+
for i, result in enumerate(self.validation_results, 1):
|
|
201
|
+
level_icon = {"error": "❌", "warning": "⚠️", "info": "ℹ️"}[result.level]
|
|
202
|
+
print(f"{i:2d}. {level_icon} {result.message}")
|
|
203
|
+
if result.section:
|
|
204
|
+
print(f" Section: {result.section}")
|
|
205
|
+
if result.key:
|
|
206
|
+
print(f" Key: {result.key}")
|
|
207
|
+
if result.suggestion:
|
|
208
|
+
print(f" Suggestion: {result.suggestion}")
|
|
209
|
+
print()
|
|
210
|
+
|
|
211
|
+
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Author: Vasiliy Zdanovskiy
|
|
3
|
+
email: vasilyvz@gmail.com
|
|
4
|
+
|
|
5
|
+
File validation utilities for MCP Proxy Adapter configuration validation.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import ssl
|
|
10
|
+
|
|
11
|
+
from .validation_result import ValidationResult
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class FileValidator:
|
|
15
|
+
"""Validator for file-related configuration settings."""
|
|
16
|
+
|
|
17
|
+
def __init__(self, config_data: Dict[str, Any]):
|
|
18
|
+
self.config_data = config_data
|
|
19
|
+
self.validation_results: List[ValidationResult] = []
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _get_nested_value_safe(self, key: str, default: Any = None) -> Any:
|
|
26
|
+
"""Safely get a nested value from configuration."""
|
|
27
|
+
keys = key.split('.')
|
|
28
|
+
value = self.config_data
|
|
29
|
+
|
|
30
|
+
for k in keys:
|
|
31
|
+
if isinstance(value, dict) and k in value:
|
|
32
|
+
value = value[k]
|
|
33
|
+
else:
|
|
34
|
+
return default
|
|
35
|
+
|
|
36
|
+
return value
|
|
37
|
+
|
|
38
|
+
def _has_nested_key(self, key: str) -> bool:
|
|
39
|
+
"""Check if a nested key exists in configuration."""
|
|
40
|
+
keys = key.split('.')
|
|
41
|
+
value = self.config_data
|
|
42
|
+
|
|
43
|
+
for k in keys:
|
|
44
|
+
if isinstance(value, dict) and k in value:
|
|
45
|
+
value = value[k]
|
|
46
|
+
else:
|
|
47
|
+
return False
|
|
48
|
+
|
|
49
|
+
return True
|
|
50
|
+
|
|
51
|
+
def _is_file_required_for_enabled_features(self, file_key: str) -> bool:
|
|
52
|
+
"""Check if a file is required based on enabled features."""
|
|
53
|
+
# SSL files are required if SSL is enabled
|
|
54
|
+
if file_key.startswith("ssl.") or file_key.startswith("transport.ssl."):
|
|
55
|
+
return self._get_nested_value_safe("ssl.enabled", False)
|
|
56
|
+
|
|
57
|
+
# Proxy registration files are required if proxy registration is enabled
|
|
58
|
+
if file_key.startswith("proxy_registration."):
|
|
59
|
+
return self._get_nested_value_safe("proxy_registration.enabled", False)
|
|
60
|
+
|
|
61
|
+
# Log directory is required if logging is enabled
|
|
62
|
+
if file_key == "logging.log_dir":
|
|
63
|
+
return self._get_nested_value_safe("logging.enabled", True)
|
|
64
|
+
|
|
65
|
+
# Command directories are required if commands are enabled
|
|
66
|
+
if file_key.startswith("commands."):
|
|
67
|
+
return self._get_nested_value_safe("commands.enabled", True)
|
|
68
|
+
|
|
69
|
+
# Security files are required if security is enabled
|
|
70
|
+
if file_key.startswith("security."):
|
|
71
|
+
return self._get_nested_value_safe("security.enabled", False)
|
|
72
|
+
|
|
73
|
+
return False
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Author: Vasiliy Zdanovskiy
|
|
3
|
+
email: vasilyvz@gmail.com
|
|
4
|
+
|
|
5
|
+
Protocol validation utilities for MCP Proxy Adapter configuration validation.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import re
|
|
9
|
+
from typing import Dict, List, Any
|
|
10
|
+
|
|
11
|
+
from .validation_result import ValidationResult
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ProtocolValidator:
|
|
15
|
+
"""Validator for protocol-related configuration settings."""
|
|
16
|
+
|
|
17
|
+
def __init__(self, config_data: Dict[str, Any]):
|
|
18
|
+
self.config_data = config_data
|
|
19
|
+
self.validation_results: List[ValidationResult] = []
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _validate_https_requirements(self) -> None:
|
|
24
|
+
"""Validate HTTPS-specific requirements."""
|
|
25
|
+
ssl_config = self._get_nested_value_safe("ssl", {})
|
|
26
|
+
|
|
27
|
+
if not ssl_config.get("enabled", False):
|
|
28
|
+
self.validation_results.append(ValidationResult(
|
|
29
|
+
level="error",
|
|
30
|
+
message="HTTPS protocol requires SSL to be enabled",
|
|
31
|
+
section="ssl",
|
|
32
|
+
suggestion="Set ssl.enabled to true"
|
|
33
|
+
))
|
|
34
|
+
|
|
35
|
+
# Check for required SSL files
|
|
36
|
+
if not ssl_config.get("cert_file"):
|
|
37
|
+
self.validation_results.append(ValidationResult(
|
|
38
|
+
level="error",
|
|
39
|
+
message="HTTPS protocol requires SSL certificate file",
|
|
40
|
+
section="ssl",
|
|
41
|
+
key="cert_file",
|
|
42
|
+
suggestion="Specify ssl.cert_file"
|
|
43
|
+
))
|
|
44
|
+
|
|
45
|
+
if not ssl_config.get("key_file"):
|
|
46
|
+
self.validation_results.append(ValidationResult(
|
|
47
|
+
level="error",
|
|
48
|
+
message="HTTPS protocol requires SSL key file",
|
|
49
|
+
section="ssl",
|
|
50
|
+
key="key_file",
|
|
51
|
+
suggestion="Specify ssl.key_file"
|
|
52
|
+
))
|
|
53
|
+
|
|
54
|
+
def _validate_mtls_requirements(self) -> None:
|
|
55
|
+
"""Validate mTLS-specific requirements."""
|
|
56
|
+
# mTLS requires HTTPS
|
|
57
|
+
self._validate_https_requirements()
|
|
58
|
+
|
|
59
|
+
ssl_config = self._get_nested_value_safe("ssl", {})
|
|
60
|
+
|
|
61
|
+
# Check for client certificate requirements
|
|
62
|
+
if not ssl_config.get("client_cert"):
|
|
63
|
+
self.validation_results.append(ValidationResult(
|
|
64
|
+
level="error",
|
|
65
|
+
message="mTLS protocol requires client certificate file",
|
|
66
|
+
section="ssl",
|
|
67
|
+
key="client_cert",
|
|
68
|
+
suggestion="Specify ssl.client_cert"
|
|
69
|
+
))
|
|
70
|
+
|
|
71
|
+
if not ssl_config.get("client_key"):
|
|
72
|
+
self.validation_results.append(ValidationResult(
|
|
73
|
+
level="error",
|
|
74
|
+
message="mTLS protocol requires client key file",
|
|
75
|
+
section="ssl",
|
|
76
|
+
key="client_key",
|
|
77
|
+
suggestion="Specify ssl.client_key"
|
|
78
|
+
))
|
|
79
|
+
|
|
80
|
+
# Check for client verification
|
|
81
|
+
if not ssl_config.get("verify_client", False):
|
|
82
|
+
self.validation_results.append(ValidationResult(
|
|
83
|
+
level="warning",
|
|
84
|
+
message="mTLS protocol should have client verification enabled",
|
|
85
|
+
section="ssl",
|
|
86
|
+
key="verify_client",
|
|
87
|
+
suggestion="Set ssl.verify_client to true"
|
|
88
|
+
))
|
|
89
|
+
|
|
90
|
+
def _validate_feature_flags(self) -> None:
|
|
91
|
+
"""Validate feature flags based on protocol."""
|
|
92
|
+
protocol = self._get_nested_value_safe("server.protocol", "http")
|
|
93
|
+
|
|
94
|
+
# Check if features are compatible with protocol
|
|
95
|
+
if protocol == "http":
|
|
96
|
+
# HTTP doesn't support SSL features
|
|
97
|
+
if self._get_nested_value_safe("ssl.enabled", False):
|
|
98
|
+
self.validation_results.append(ValidationResult(
|
|
99
|
+
level="warning",
|
|
100
|
+
message="SSL is enabled but protocol is HTTP. Consider using HTTPS",
|
|
101
|
+
section="ssl",
|
|
102
|
+
suggestion="Change protocol to https or disable SSL"
|
|
103
|
+
))
|
|
104
|
+
|
|
105
|
+
# Check transport configuration
|
|
106
|
+
transport_config = self._get_nested_value_safe("transport", {})
|
|
107
|
+
if transport_config:
|
|
108
|
+
transport_ssl = transport_config.get("ssl", {})
|
|
109
|
+
if transport_ssl.get("enabled", False) and protocol == "http":
|
|
110
|
+
self.validation_results.append(ValidationResult(
|
|
111
|
+
level="warning",
|
|
112
|
+
message="Transport SSL is enabled but protocol is HTTP",
|
|
113
|
+
section="transport.ssl",
|
|
114
|
+
suggestion="Change protocol to https or disable transport SSL"
|
|
115
|
+
))
|
|
116
|
+
|
|
117
|
+
def _validate_server_section(self) -> None:
|
|
118
|
+
"""Validate server section requirements."""
|
|
119
|
+
server_config = self.config_data.get("server", {})
|
|
120
|
+
|
|
121
|
+
# Check required fields
|
|
122
|
+
if "host" not in server_config:
|
|
123
|
+
self.validation_results.append(ValidationResult(
|
|
124
|
+
level="error",
|
|
125
|
+
message="Server host is required",
|
|
126
|
+
section="server",
|
|
127
|
+
key="host",
|
|
128
|
+
suggestion="Add host field to server section"
|
|
129
|
+
))
|
|
130
|
+
|
|
131
|
+
if "port" not in server_config:
|
|
132
|
+
self.validation_results.append(ValidationResult(
|
|
133
|
+
level="error",
|
|
134
|
+
message="Server port is required",
|
|
135
|
+
section="server",
|
|
136
|
+
key="port",
|
|
137
|
+
suggestion="Add port field to server section"
|
|
138
|
+
))
|
|
139
|
+
|
|
140
|
+
# Validate port number
|
|
141
|
+
port = server_config.get("port")
|
|
142
|
+
if port is not None:
|
|
143
|
+
if not isinstance(port, int) or not (1 <= port <= 65535):
|
|
144
|
+
self.validation_results.append(ValidationResult(
|
|
145
|
+
level="error",
|
|
146
|
+
message=f"Invalid port number: {port}. Must be between 1 and 65535",
|
|
147
|
+
section="server",
|
|
148
|
+
key="port"
|
|
149
|
+
))
|
|
150
|
+
|
|
151
|
+
# Validate host format
|
|
152
|
+
host = server_config.get("host")
|
|
153
|
+
if host is not None:
|
|
154
|
+
if not self._is_valid_host(host):
|
|
155
|
+
self.validation_results.append(ValidationResult(
|
|
156
|
+
level="error",
|
|
157
|
+
message=f"Invalid host format: {host}",
|
|
158
|
+
section="server",
|
|
159
|
+
key="host",
|
|
160
|
+
suggestion="Use a valid hostname or IP address"
|
|
161
|
+
))
|
|
162
|
+
|
|
163
|
+
def _is_valid_host(self, host: str) -> bool:
|
|
164
|
+
"""Check if host has valid format."""
|
|
165
|
+
# Check for localhost
|
|
166
|
+
if host in ["localhost", "127.0.0.1", "::1", "0.0.0.0"]:
|
|
167
|
+
return True
|
|
168
|
+
|
|
169
|
+
# Check for IP address
|
|
170
|
+
ip_pattern = r'^(\d{1,3}\.){3}\d{1,3}$'
|
|
171
|
+
if re.match(ip_pattern, host):
|
|
172
|
+
# Validate IP address ranges
|
|
173
|
+
parts = host.split('.')
|
|
174
|
+
return all(0 <= int(part) <= 255 for part in parts)
|
|
175
|
+
|
|
176
|
+
# Check for hostname (basic validation)
|
|
177
|
+
hostname_pattern = r'^[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?)*$'
|
|
178
|
+
return bool(re.match(hostname_pattern, host))
|
|
179
|
+
|
|
180
|
+
def _get_nested_value_safe(self, key: str, default: Any = None) -> Any:
|
|
181
|
+
"""Safely get a nested value from configuration."""
|
|
182
|
+
keys = key.split('.')
|
|
183
|
+
value = self.config_data
|
|
184
|
+
|
|
185
|
+
for k in keys:
|
|
186
|
+
if isinstance(value, dict) and k in value:
|
|
187
|
+
value = value[k]
|
|
188
|
+
else:
|
|
189
|
+
return default
|
|
190
|
+
|
|
191
|
+
return value
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Author: Vasiliy Zdanovskiy
|
|
3
|
+
email: vasilyvz@gmail.com
|
|
4
|
+
|
|
5
|
+
Security validation utilities for MCP Proxy Adapter configuration validation.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
from typing import Dict, List, Any
|
|
10
|
+
|
|
11
|
+
from .validation_result import ValidationResult
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class SecurityValidator:
|
|
15
|
+
"""Validator for security-related configuration settings."""
|
|
16
|
+
|
|
17
|
+
def __init__(self, config_data: Dict[str, Any]):
|
|
18
|
+
self.config_data = config_data
|
|
19
|
+
self.validation_results: List[ValidationResult] = []
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _get_nested_value_safe(self, key: str, default: Any = None) -> Any:
|
|
26
|
+
"""Safely get a nested value from configuration."""
|
|
27
|
+
keys = key.split('.')
|
|
28
|
+
value = self.config_data
|
|
29
|
+
|
|
30
|
+
for k in keys:
|
|
31
|
+
if isinstance(value, dict) and k in value:
|
|
32
|
+
value = value[k]
|
|
33
|
+
else:
|
|
34
|
+
return default
|
|
35
|
+
|
|
36
|
+
return value
|
|
37
|
+
|
|
38
|
+
def _has_nested_key(self, key: str) -> bool:
|
|
39
|
+
"""Check if a nested key exists in configuration."""
|
|
40
|
+
keys = key.split('.')
|
|
41
|
+
value = self.config_data
|
|
42
|
+
|
|
43
|
+
for k in keys:
|
|
44
|
+
if isinstance(value, dict) and k in value:
|
|
45
|
+
value = value[k]
|
|
46
|
+
else:
|
|
47
|
+
return False
|
|
48
|
+
|
|
49
|
+
return True
|
|
50
|
+
|
|
51
|
+
def _is_valid_url(self, url: str) -> bool:
|
|
52
|
+
"""Check if URL has valid format."""
|
|
53
|
+
try:
|
|
54
|
+
from urllib.parse import urlparse
|
|
55
|
+
parsed = urlparse(url)
|
|
56
|
+
return bool(parsed.scheme and parsed.netloc)
|
|
57
|
+
except Exception:
|
|
58
|
+
return False
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Author: Vasiliy Zdanovskiy
|
|
3
|
+
email: vasilyvz@gmail.com
|
|
4
|
+
|
|
5
|
+
Validation result classes for MCP Proxy Adapter configuration validation.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from enum import Enum
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from typing import Optional
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ValidationLevel(Enum):
|
|
14
|
+
"""Validation severity levels."""
|
|
15
|
+
ERROR = "error"
|
|
16
|
+
WARNING = "warning"
|
|
17
|
+
INFO = "info"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class ValidationResult:
|
|
22
|
+
"""Result of configuration validation."""
|
|
23
|
+
level: str # "error", "warning", "info"
|
|
24
|
+
message: str
|
|
25
|
+
section: Optional[str] = None
|
|
26
|
+
key: Optional[str] = None
|
|
27
|
+
suggestion: Optional[str] = None
|