mcp-proxy-adapter 6.9.28__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 -913
- 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.28.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.28.dist-info → mcp_proxy_adapter-6.9.29.dist-info}/entry_points.txt +1 -1
- mcp_proxy_adapter-6.9.28.dist-info/RECORD +0 -149
- {mcp_proxy_adapter-6.9.28.dist-info → mcp_proxy_adapter-6.9.29.dist-info}/WHEEL +0 -0
- {mcp_proxy_adapter-6.9.28.dist-info → mcp_proxy_adapter-6.9.29.dist-info}/top_level.txt +0 -0
|
@@ -54,80 +54,7 @@ class TransportManager:
|
|
|
54
54
|
self._config: Optional[TransportConfig] = None
|
|
55
55
|
self._current_transport: Optional[TransportType] = None
|
|
56
56
|
|
|
57
|
-
def load_config(self, config: Dict[str, Any]) -> bool:
|
|
58
|
-
"""
|
|
59
|
-
Load transport configuration from config dict.
|
|
60
|
-
|
|
61
|
-
Args:
|
|
62
|
-
config: Configuration dictionary
|
|
63
57
|
|
|
64
|
-
Returns:
|
|
65
|
-
True if config loaded successfully, False otherwise
|
|
66
|
-
"""
|
|
67
|
-
try:
|
|
68
|
-
transport_config = config.get("transport", {})
|
|
69
|
-
|
|
70
|
-
# Get transport type
|
|
71
|
-
transport_type_str = transport_config.get("type", "http").lower()
|
|
72
|
-
try:
|
|
73
|
-
transport_type = TransportType(transport_type_str)
|
|
74
|
-
except ValueError:
|
|
75
|
-
get_global_logger().error(f"Invalid transport type: {transport_type_str}")
|
|
76
|
-
return False
|
|
77
|
-
|
|
78
|
-
# Get port (use default if not specified)
|
|
79
|
-
port = transport_config.get("port")
|
|
80
|
-
if port is None:
|
|
81
|
-
port = self.DEFAULT_PORTS.get(transport_type, 8000)
|
|
82
|
-
|
|
83
|
-
# Get SSL configuration
|
|
84
|
-
ssl_config = transport_config.get("ssl", {})
|
|
85
|
-
ssl_enabled = ssl_config.get("enabled", False)
|
|
86
|
-
|
|
87
|
-
# Validate SSL requirements
|
|
88
|
-
if (
|
|
89
|
-
transport_type in [TransportType.HTTPS, TransportType.MTLS]
|
|
90
|
-
and not ssl_enabled
|
|
91
|
-
):
|
|
92
|
-
get_global_logger().error(
|
|
93
|
-
f"SSL must be enabled for transport type: {transport_type.value}"
|
|
94
|
-
)
|
|
95
|
-
return False
|
|
96
|
-
|
|
97
|
-
if transport_type == TransportType.HTTP and ssl_enabled:
|
|
98
|
-
get_global_logger().warning("SSL enabled for HTTP transport - this may cause issues")
|
|
99
|
-
|
|
100
|
-
# Create transport config
|
|
101
|
-
self._config = TransportConfig(
|
|
102
|
-
type=transport_type,
|
|
103
|
-
port=port,
|
|
104
|
-
ssl_enabled=ssl_enabled,
|
|
105
|
-
cert_file=ssl_config.get("cert_file") if ssl_enabled else None,
|
|
106
|
-
key_file=ssl_config.get("key_file") if ssl_enabled else None,
|
|
107
|
-
ca_cert=ssl_config.get("ca_cert") if ssl_enabled else None,
|
|
108
|
-
verify_client=ssl_config.get("verify_client", False),
|
|
109
|
-
client_cert_required=ssl_config.get("client_cert_required", False),
|
|
110
|
-
)
|
|
111
|
-
|
|
112
|
-
self._current_transport = transport_type
|
|
113
|
-
|
|
114
|
-
get_global_logger().info(
|
|
115
|
-
f"Transport config loaded: {transport_type.value} on port {port}"
|
|
116
|
-
)
|
|
117
|
-
return True
|
|
118
|
-
|
|
119
|
-
except Exception as e:
|
|
120
|
-
get_global_logger().error(f"Failed to load transport config: {e}")
|
|
121
|
-
return False
|
|
122
|
-
|
|
123
|
-
def get_transport_type(self) -> Optional[TransportType]:
|
|
124
|
-
"""
|
|
125
|
-
Get current transport type.
|
|
126
|
-
|
|
127
|
-
Returns:
|
|
128
|
-
Current transport type or None if not configured
|
|
129
|
-
"""
|
|
130
|
-
return self._current_transport
|
|
131
58
|
|
|
132
59
|
def get_port(self) -> Optional[int]:
|
|
133
60
|
"""
|
|
@@ -192,63 +119,7 @@ class TransportManager:
|
|
|
192
119
|
"""
|
|
193
120
|
return self._current_transport == TransportType.HTTP
|
|
194
121
|
|
|
195
|
-
def get_transport_info(self) -> Dict[str, Any]:
|
|
196
|
-
"""
|
|
197
|
-
Get transport information.
|
|
198
|
-
|
|
199
|
-
Returns:
|
|
200
|
-
Dictionary with transport information
|
|
201
|
-
"""
|
|
202
|
-
if not self._config:
|
|
203
|
-
return {"error": "Transport not configured"}
|
|
204
|
-
|
|
205
|
-
return {
|
|
206
|
-
"type": self._config.type.value,
|
|
207
|
-
"port": self._config.port,
|
|
208
|
-
"ssl_enabled": self._config.ssl_enabled,
|
|
209
|
-
"is_mtls": self.is_mtls(),
|
|
210
|
-
"is_https": self.is_https(),
|
|
211
|
-
"is_http": self.is_http(),
|
|
212
|
-
"ssl_config": self.get_ssl_config(),
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
def validate_config(self) -> bool:
|
|
216
|
-
"""
|
|
217
|
-
Validate current transport configuration.
|
|
218
|
-
|
|
219
|
-
Returns:
|
|
220
|
-
True if configuration is valid, False otherwise
|
|
221
|
-
"""
|
|
222
|
-
if not self._config:
|
|
223
|
-
get_global_logger().error("Transport not configured")
|
|
224
|
-
return False
|
|
225
|
-
|
|
226
|
-
# Validate SSL requirements
|
|
227
|
-
if self._config.type in [TransportType.HTTPS, TransportType.MTLS]:
|
|
228
|
-
if not self._config.ssl_enabled:
|
|
229
|
-
get_global_logger().error(f"SSL must be enabled for {self._config.type.value}")
|
|
230
|
-
return False
|
|
231
|
-
|
|
232
|
-
if not self._config.cert_file or not self._config.key_file:
|
|
233
|
-
get_global_logger().error(
|
|
234
|
-
f"SSL certificate and key required for {self._config.type.value}"
|
|
235
|
-
)
|
|
236
|
-
return False
|
|
237
|
-
|
|
238
|
-
# Validate SSL files exist
|
|
239
|
-
if not self.validate_ssl_files():
|
|
240
|
-
return False
|
|
241
|
-
|
|
242
|
-
# Validate MTLS requirements
|
|
243
|
-
if self._config.type == TransportType.MTLS:
|
|
244
|
-
if not self._config.verify_client:
|
|
245
|
-
get_global_logger().warning("MTLS transport should have client verification enabled")
|
|
246
122
|
|
|
247
|
-
if not self._config.ca_cert:
|
|
248
|
-
get_global_logger().warning("CA certificate recommended for MTLS transport")
|
|
249
|
-
|
|
250
|
-
get_global_logger().info(f"Transport configuration validated: {self._config.type.value}")
|
|
251
|
-
return True
|
|
252
123
|
|
|
253
124
|
def validate_ssl_files(self) -> bool:
|
|
254
125
|
"""
|
|
@@ -276,28 +147,6 @@ class TransportManager:
|
|
|
276
147
|
get_global_logger().info(f"All SSL files validated successfully: {files_to_check}")
|
|
277
148
|
return True
|
|
278
149
|
|
|
279
|
-
def get_hypercorn_config(self) -> Dict[str, Any]:
|
|
280
|
-
"""
|
|
281
|
-
Get configuration for hypercorn.
|
|
282
|
-
|
|
283
|
-
Returns:
|
|
284
|
-
Hypercorn configuration dictionary
|
|
285
|
-
"""
|
|
286
|
-
config = {
|
|
287
|
-
"host": "0.0.0.0", # Can be moved to settings
|
|
288
|
-
"port": self.get_port(),
|
|
289
|
-
"log_level": "info",
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
if self.is_ssl_enabled():
|
|
293
|
-
ssl_config = self.get_ssl_config()
|
|
294
|
-
if ssl_config:
|
|
295
|
-
from mcp_proxy_adapter.core.ssl_utils import SSLUtils
|
|
296
|
-
|
|
297
|
-
hypercorn_ssl = SSLUtils.get_ssl_config_for_hypercorn(ssl_config)
|
|
298
|
-
config.update(hypercorn_ssl)
|
|
299
|
-
|
|
300
|
-
return config
|
|
301
150
|
|
|
302
151
|
|
|
303
152
|
# Global transport manager instance
|
|
@@ -10,9 +10,9 @@ to SecurityConfig format used by mcp_security_framework.
|
|
|
10
10
|
|
|
11
11
|
import json
|
|
12
12
|
import logging
|
|
13
|
-
from typing import Dict, Any, Optional, List, Union
|
|
14
13
|
from pathlib import Path
|
|
15
14
|
from dataclasses import dataclass
|
|
15
|
+
from typing import List, Dict, Any
|
|
16
16
|
|
|
17
17
|
# Import mcp_security_framework components
|
|
18
18
|
try:
|
|
@@ -70,53 +70,6 @@ class UnifiedConfigAdapter:
|
|
|
70
70
|
self.validation_errors = []
|
|
71
71
|
self.validation_warnings = []
|
|
72
72
|
|
|
73
|
-
def convert_to_security_config(
|
|
74
|
-
self, config: Dict[str, Any]
|
|
75
|
-
) -> Optional[SecurityConfig]:
|
|
76
|
-
"""
|
|
77
|
-
Convert mcp_proxy_adapter configuration to SecurityConfig.
|
|
78
|
-
|
|
79
|
-
Args:
|
|
80
|
-
config: mcp_proxy_adapter configuration dictionary
|
|
81
|
-
|
|
82
|
-
Returns:
|
|
83
|
-
SecurityConfig instance or None if conversion failed
|
|
84
|
-
"""
|
|
85
|
-
if not SECURITY_FRAMEWORK_AVAILABLE:
|
|
86
|
-
get_global_logger().error(
|
|
87
|
-
"mcp_security_framework not available, cannot convert configuration"
|
|
88
|
-
)
|
|
89
|
-
return None
|
|
90
|
-
|
|
91
|
-
try:
|
|
92
|
-
# Validate configuration first
|
|
93
|
-
validation_result = self.validate_configuration(config)
|
|
94
|
-
if not validation_result.is_valid:
|
|
95
|
-
get_global_logger().error(
|
|
96
|
-
f"Configuration validation failed: {validation_result.errors}"
|
|
97
|
-
)
|
|
98
|
-
return None
|
|
99
|
-
|
|
100
|
-
# Convert configuration sections
|
|
101
|
-
auth_config = self._convert_auth_config(config)
|
|
102
|
-
ssl_config = self._convert_ssl_config(config)
|
|
103
|
-
permission_config = self._convert_permission_config(config)
|
|
104
|
-
rate_limit_config = self._convert_rate_limit_config(config)
|
|
105
|
-
|
|
106
|
-
# Create SecurityConfig
|
|
107
|
-
security_config = SecurityConfig(
|
|
108
|
-
auth=auth_config,
|
|
109
|
-
ssl=ssl_config,
|
|
110
|
-
permissions=permission_config,
|
|
111
|
-
rate_limit=rate_limit_config,
|
|
112
|
-
)
|
|
113
|
-
|
|
114
|
-
get_global_logger().info("Configuration successfully converted to SecurityConfig")
|
|
115
|
-
return security_config
|
|
116
|
-
|
|
117
|
-
except Exception as e:
|
|
118
|
-
get_global_logger().error(f"Failed to convert configuration: {e}")
|
|
119
|
-
return None
|
|
120
73
|
|
|
121
74
|
def validate_configuration(self, config: Dict[str, Any]) -> ValidationResult:
|
|
122
75
|
"""
|
|
@@ -479,151 +432,6 @@ class UnifiedConfigAdapter:
|
|
|
479
432
|
|
|
480
433
|
return rate_limit_config
|
|
481
434
|
|
|
482
|
-
def get_public_paths(self, config: Dict[str, Any]) -> List[str]:
|
|
483
|
-
"""
|
|
484
|
-
Get public paths from configuration.
|
|
485
435
|
|
|
486
|
-
Args:
|
|
487
|
-
config: Configuration dictionary
|
|
488
436
|
|
|
489
|
-
Returns:
|
|
490
|
-
List of public paths
|
|
491
|
-
"""
|
|
492
|
-
security_config = config.get("security", {})
|
|
493
|
-
|
|
494
|
-
# Ensure security_config is a dictionary
|
|
495
|
-
if not isinstance(security_config, dict):
|
|
496
|
-
return ["/health", "/docs", "/redoc", "/openapi.json", "/favicon.ico"]
|
|
497
|
-
|
|
498
|
-
public_paths = security_config.get("public_paths", [])
|
|
499
|
-
|
|
500
|
-
# Add default public paths if none specified
|
|
501
|
-
if not public_paths:
|
|
502
|
-
public_paths = [
|
|
503
|
-
"/health",
|
|
504
|
-
"/docs",
|
|
505
|
-
"/redoc",
|
|
506
|
-
"/openapi.json",
|
|
507
|
-
"/favicon.ico",
|
|
508
|
-
]
|
|
509
437
|
|
|
510
|
-
return public_paths
|
|
511
|
-
|
|
512
|
-
def get_security_enabled(self, config: Dict[str, Any]) -> bool:
|
|
513
|
-
"""
|
|
514
|
-
Check if security is enabled in configuration.
|
|
515
|
-
|
|
516
|
-
Args:
|
|
517
|
-
config: Configuration dictionary
|
|
518
|
-
|
|
519
|
-
Returns:
|
|
520
|
-
True if security is enabled
|
|
521
|
-
"""
|
|
522
|
-
security_config = config.get("security", {})
|
|
523
|
-
|
|
524
|
-
# Ensure security_config is a dictionary
|
|
525
|
-
if not isinstance(security_config, dict):
|
|
526
|
-
return True # Default to enabled for safety
|
|
527
|
-
|
|
528
|
-
return security_config.get("enabled", True)
|
|
529
|
-
|
|
530
|
-
def migrate_legacy_config(self, config: Dict[str, Any]) -> Dict[str, Any]:
|
|
531
|
-
"""
|
|
532
|
-
Migrate legacy configuration to new format.
|
|
533
|
-
|
|
534
|
-
Args:
|
|
535
|
-
config: Legacy configuration dictionary
|
|
536
|
-
|
|
537
|
-
Returns:
|
|
538
|
-
Migrated configuration dictionary
|
|
539
|
-
"""
|
|
540
|
-
migrated_config = config.copy()
|
|
541
|
-
|
|
542
|
-
# Create security section if it doesn't exist
|
|
543
|
-
if "security" not in migrated_config:
|
|
544
|
-
migrated_config["security"] = {}
|
|
545
|
-
|
|
546
|
-
security_config = migrated_config["security"]
|
|
547
|
-
|
|
548
|
-
# Migrate SSL configuration
|
|
549
|
-
if "ssl" in migrated_config and "ssl" not in security_config:
|
|
550
|
-
security_config["ssl"] = migrated_config["ssl"]
|
|
551
|
-
# Don't remove legacy SSL yet for backward compatibility
|
|
552
|
-
|
|
553
|
-
# Migrate roles configuration
|
|
554
|
-
if "roles" in migrated_config and "permissions" not in security_config:
|
|
555
|
-
security_config["permissions"] = migrated_config["roles"]
|
|
556
|
-
# Don't remove legacy roles yet for backward compatibility
|
|
557
|
-
|
|
558
|
-
# Migrate auth_enabled flag
|
|
559
|
-
if "auth_enabled" in migrated_config and "auth" not in security_config:
|
|
560
|
-
security_config["auth"] = {"enabled": migrated_config["auth_enabled"]}
|
|
561
|
-
|
|
562
|
-
# Migrate rate_limit_enabled flag
|
|
563
|
-
if (
|
|
564
|
-
"rate_limit_enabled" in migrated_config
|
|
565
|
-
and "rate_limit" not in security_config
|
|
566
|
-
):
|
|
567
|
-
security_config["rate_limit"] = {
|
|
568
|
-
"enabled": migrated_config["rate_limit_enabled"]
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
get_global_logger().info("Legacy configuration migrated to new format")
|
|
572
|
-
return migrated_config
|
|
573
|
-
|
|
574
|
-
def get_default_config(self) -> Dict[str, Any]:
|
|
575
|
-
"""
|
|
576
|
-
Get default configuration.
|
|
577
|
-
|
|
578
|
-
Returns:
|
|
579
|
-
Default configuration dictionary
|
|
580
|
-
"""
|
|
581
|
-
return {
|
|
582
|
-
"security": {
|
|
583
|
-
"enabled": True,
|
|
584
|
-
"auth": {
|
|
585
|
-
"enabled": True,
|
|
586
|
-
"methods": ["api_key"],
|
|
587
|
-
"api_keys": {},
|
|
588
|
-
"jwt_secret": "",
|
|
589
|
-
"jwt_algorithm": "HS256",
|
|
590
|
-
"jwt_expiration": 3600,
|
|
591
|
-
},
|
|
592
|
-
"ssl": {
|
|
593
|
-
"enabled": False,
|
|
594
|
-
"cert_file": None,
|
|
595
|
-
"key_file": None,
|
|
596
|
-
"ca_cert": None,
|
|
597
|
-
"min_tls_version": "TLSv1.2",
|
|
598
|
-
"verify_client": False,
|
|
599
|
-
"client_cert_required": False,
|
|
600
|
-
"cipher_suites": [],
|
|
601
|
-
},
|
|
602
|
-
"permissions": {
|
|
603
|
-
"enabled": True,
|
|
604
|
-
"roles_file": "roles.json",
|
|
605
|
-
"default_role": "user",
|
|
606
|
-
"deny_by_default": True,
|
|
607
|
-
"role_mappings": {},
|
|
608
|
-
},
|
|
609
|
-
"rate_limit": {
|
|
610
|
-
"enabled": True,
|
|
611
|
-
"requests_per_minute": 60,
|
|
612
|
-
"requests_per_hour": 1000,
|
|
613
|
-
"requests_per_day": 10000,
|
|
614
|
-
"burst_limit": 10,
|
|
615
|
-
"by_ip": True,
|
|
616
|
-
"by_user": True,
|
|
617
|
-
"by_endpoint": False,
|
|
618
|
-
"exempt_roles": [],
|
|
619
|
-
"exempt_endpoints": [],
|
|
620
|
-
},
|
|
621
|
-
"public_paths": [
|
|
622
|
-
"/health",
|
|
623
|
-
"/docs",
|
|
624
|
-
"/redoc",
|
|
625
|
-
"/openapi.json",
|
|
626
|
-
"/favicon.ico",
|
|
627
|
-
],
|
|
628
|
-
}
|
|
629
|
-
}
|
mcp_proxy_adapter/core/utils.py
CHANGED
|
@@ -10,135 +10,25 @@ import sys
|
|
|
10
10
|
import time
|
|
11
11
|
import uuid
|
|
12
12
|
from datetime import datetime, timezone
|
|
13
|
-
from typing import
|
|
13
|
+
from typing import Optional, Dict, Any, List
|
|
14
14
|
|
|
15
15
|
from mcp_proxy_adapter.core.logging import get_global_logger
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
def generate_id() -> str:
|
|
19
|
-
"""
|
|
20
|
-
Generates a unique identifier.
|
|
21
18
|
|
|
22
|
-
Returns:
|
|
23
|
-
String with unique identifier.
|
|
24
|
-
"""
|
|
25
|
-
return str(uuid.uuid4())
|
|
26
19
|
|
|
27
20
|
|
|
28
|
-
def get_timestamp() -> int:
|
|
29
|
-
"""
|
|
30
|
-
Returns current timestamp in milliseconds.
|
|
31
21
|
|
|
32
|
-
Returns:
|
|
33
|
-
Integer - timestamp in milliseconds.
|
|
34
|
-
"""
|
|
35
|
-
return int(time.time() * 1000)
|
|
36
22
|
|
|
37
23
|
|
|
38
|
-
def format_datetime(
|
|
39
|
-
dt: Optional[datetime] = None, format_str: str = "%Y-%m-%dT%H:%M:%S.%fZ"
|
|
40
|
-
) -> str:
|
|
41
|
-
"""
|
|
42
|
-
Formats date and time as string.
|
|
43
24
|
|
|
44
|
-
Args:
|
|
45
|
-
dt: Datetime object to format. If None, current time is used.
|
|
46
|
-
format_str: Format string for output.
|
|
47
25
|
|
|
48
|
-
Returns:
|
|
49
|
-
Formatted date/time string.
|
|
50
|
-
"""
|
|
51
|
-
dt = dt or datetime.now(timezone.utc)
|
|
52
|
-
return dt.strftime(format_str)
|
|
53
26
|
|
|
54
27
|
|
|
55
|
-
def parse_datetime(dt_str: str, format_str: str = "%Y-%m-%dT%H:%M:%S.%fZ") -> datetime:
|
|
56
|
-
"""
|
|
57
|
-
Parses date/time string into datetime object.
|
|
58
28
|
|
|
59
|
-
Args:
|
|
60
|
-
dt_str: Date/time string.
|
|
61
|
-
format_str: Date/time string format.
|
|
62
29
|
|
|
63
|
-
Returns:
|
|
64
|
-
Datetime object.
|
|
65
|
-
"""
|
|
66
|
-
return datetime.strptime(dt_str, format_str)
|
|
67
30
|
|
|
68
31
|
|
|
69
|
-
def safe_json_loads(s: str, default: Any = None) -> Any:
|
|
70
|
-
"""
|
|
71
|
-
Safe JSON string loading.
|
|
72
|
-
|
|
73
|
-
Args:
|
|
74
|
-
s: JSON string to load.
|
|
75
|
-
default: Default value on parsing error.
|
|
76
|
-
|
|
77
|
-
Returns:
|
|
78
|
-
Loaded object or default value on error.
|
|
79
|
-
"""
|
|
80
|
-
try:
|
|
81
|
-
return json.loads(s)
|
|
82
|
-
except Exception as e:
|
|
83
|
-
get_global_logger().error(f"Error parsing JSON: {e}")
|
|
84
|
-
return default
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
def safe_json_dumps(obj: Any, default: str = "{}", indent: Optional[int] = None) -> str:
|
|
88
|
-
"""
|
|
89
|
-
Safe object conversion to JSON string.
|
|
90
|
-
|
|
91
|
-
Args:
|
|
92
|
-
obj: Object to convert.
|
|
93
|
-
default: Default string on serialization error.
|
|
94
|
-
indent: Indentation for JSON formatting.
|
|
95
|
-
|
|
96
|
-
Returns:
|
|
97
|
-
JSON string or default string on error.
|
|
98
|
-
"""
|
|
99
|
-
try:
|
|
100
|
-
return json.dumps(obj, ensure_ascii=False, indent=indent)
|
|
101
|
-
except Exception as e:
|
|
102
|
-
get_global_logger().error(f"Error serializing to JSON: {e}")
|
|
103
|
-
return default
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
def calculate_hash(data: Union[str, bytes], algorithm: str = "sha256") -> str:
|
|
107
|
-
"""
|
|
108
|
-
Calculates hash for data.
|
|
109
|
-
|
|
110
|
-
Args:
|
|
111
|
-
data: Data to hash (string or bytes).
|
|
112
|
-
algorithm: Hashing algorithm (md5, sha1, sha256, etc.).
|
|
113
|
-
|
|
114
|
-
Returns:
|
|
115
|
-
String with hash in hexadecimal format.
|
|
116
|
-
"""
|
|
117
|
-
if isinstance(data, str):
|
|
118
|
-
data = data.encode("utf-8")
|
|
119
|
-
|
|
120
|
-
hash_obj = hashlib.new(algorithm)
|
|
121
|
-
hash_obj.update(data)
|
|
122
|
-
return hash_obj.hexdigest()
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
def ensure_directory(path: str) -> bool:
|
|
126
|
-
"""
|
|
127
|
-
Checks directory existence and creates it if necessary.
|
|
128
|
-
|
|
129
|
-
Args:
|
|
130
|
-
path: Path to directory.
|
|
131
|
-
|
|
132
|
-
Returns:
|
|
133
|
-
True if directory exists or was successfully created, otherwise False.
|
|
134
|
-
"""
|
|
135
|
-
try:
|
|
136
|
-
if not os.path.exists(path):
|
|
137
|
-
os.makedirs(path, exist_ok=True)
|
|
138
|
-
return True
|
|
139
|
-
except Exception as e:
|
|
140
|
-
get_global_logger().error(f"Error creating directory {path}: {e}")
|
|
141
|
-
return False
|
|
142
32
|
|
|
143
33
|
|
|
144
34
|
def check_port_availability(host: str, port: int, timeout: float = 1.0) -> bool:
|
|
@@ -207,76 +97,5 @@ def get_port_usage_info(port: int) -> str:
|
|
|
207
97
|
return f"Port {port} is in use (unable to get process info: {e})"
|
|
208
98
|
|
|
209
99
|
|
|
210
|
-
def handle_port_conflict(host: str, port: int) -> None:
|
|
211
|
-
"""
|
|
212
|
-
Handles port conflict with user-friendly error message and suggestions.
|
|
213
|
-
This is for MAIN server port conflicts - application must exit.
|
|
214
|
-
|
|
215
|
-
Args:
|
|
216
|
-
host: Host address
|
|
217
|
-
port: Port number that's in conflict
|
|
218
|
-
"""
|
|
219
|
-
print(f"❌ ERROR: Port {port} is already in use on {host}")
|
|
220
|
-
print(f"💡 Suggestions:")
|
|
221
|
-
print(f" 1. Choose a different port: --port <different_port>")
|
|
222
|
-
print(f" 2. Stop the conflicting service")
|
|
223
|
-
print(f" 3. Check what's using the port:")
|
|
224
|
-
print(f" lsof -i :{port}")
|
|
225
|
-
print(f" netstat -tulpn | grep :{port}")
|
|
226
|
-
|
|
227
|
-
# Try to get more detailed info about port usage
|
|
228
|
-
usage_info = get_port_usage_info(port)
|
|
229
|
-
print(f"🔍 Port usage details:")
|
|
230
|
-
print(f" {usage_info}")
|
|
231
|
-
|
|
232
|
-
# Try to find an alternative port
|
|
233
|
-
alt_port = find_available_port(host, port + 1, 10)
|
|
234
|
-
if alt_port:
|
|
235
|
-
print(f"💡 Alternative port suggestion: {alt_port}")
|
|
236
|
-
print(f" Try: --port {alt_port}")
|
|
237
|
-
else:
|
|
238
|
-
print(f"💡 Try ports in range {port + 1}-{port + 20}")
|
|
239
|
-
|
|
240
|
-
print(f"🛑 Application cannot start due to port conflict")
|
|
241
|
-
sys.exit(1)
|
|
242
100
|
|
|
243
101
|
|
|
244
|
-
def find_port_for_internal_server(host: str, preferred_port: int) -> int:
|
|
245
|
-
"""
|
|
246
|
-
Finds an available port for internal server (mTLS, etc.).
|
|
247
|
-
If preferred port is occupied, finds any available port.
|
|
248
|
-
|
|
249
|
-
Args:
|
|
250
|
-
host: Host address
|
|
251
|
-
preferred_port: Preferred port number
|
|
252
|
-
|
|
253
|
-
Returns:
|
|
254
|
-
Available port number
|
|
255
|
-
"""
|
|
256
|
-
# First try the preferred port
|
|
257
|
-
if check_port_availability(host, preferred_port):
|
|
258
|
-
print(f"✅ Internal server port {preferred_port} is available")
|
|
259
|
-
return preferred_port
|
|
260
|
-
|
|
261
|
-
# If preferred port is occupied, find any available port
|
|
262
|
-
print(f"⚠️ Internal server preferred port {preferred_port} is occupied, searching for alternative...")
|
|
263
|
-
|
|
264
|
-
alt_port = find_available_port(host, preferred_port + 1, 50)
|
|
265
|
-
if alt_port:
|
|
266
|
-
print(f"✅ Found alternative port for internal server: {alt_port}")
|
|
267
|
-
return alt_port
|
|
268
|
-
|
|
269
|
-
# If no port found in range, try from 9000
|
|
270
|
-
alt_port = find_available_port(host, 9000, 100)
|
|
271
|
-
if alt_port:
|
|
272
|
-
print(f"✅ Found alternative port for internal server: {alt_port}")
|
|
273
|
-
return alt_port
|
|
274
|
-
|
|
275
|
-
# Last resort - try from 10000
|
|
276
|
-
alt_port = find_available_port(host, 10000, 100)
|
|
277
|
-
if alt_port:
|
|
278
|
-
print(f"✅ Found alternative port for internal server: {alt_port}")
|
|
279
|
-
return alt_port
|
|
280
|
-
|
|
281
|
-
# If still no port found, raise error
|
|
282
|
-
raise RuntimeError(f"Unable to find available port for internal server on {host}")
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Author: Vasiliy Zdanovskiy
|
|
3
|
+
email: vasilyvz@gmail.com
|
|
4
|
+
|
|
5
|
+
Configuration validation package for MCP Proxy Adapter.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .config_validator import ConfigValidator
|
|
9
|
+
from .validation_result import ValidationResult, ValidationLevel
|
|
10
|
+
from .file_validator import FileValidator
|
|
11
|
+
from .security_validator import SecurityValidator
|
|
12
|
+
from .protocol_validator import ProtocolValidator
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"ConfigValidator",
|
|
16
|
+
"ValidationResult",
|
|
17
|
+
"ValidationLevel",
|
|
18
|
+
"FileValidator",
|
|
19
|
+
"SecurityValidator",
|
|
20
|
+
"ProtocolValidator",
|
|
21
|
+
]
|