mcp-proxy-adapter 4.1.1__py3-none-any.whl → 6.0.1__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.
- mcp_proxy_adapter/__main__.py +32 -0
- mcp_proxy_adapter/api/app.py +290 -33
- mcp_proxy_adapter/api/handlers.py +32 -6
- mcp_proxy_adapter/api/middleware/__init__.py +38 -32
- mcp_proxy_adapter/api/middleware/command_permission_middleware.py +148 -0
- mcp_proxy_adapter/api/middleware/error_handling.py +9 -0
- mcp_proxy_adapter/api/middleware/factory.py +243 -0
- mcp_proxy_adapter/api/middleware/logging.py +32 -6
- mcp_proxy_adapter/api/middleware/protocol_middleware.py +201 -0
- mcp_proxy_adapter/api/middleware/transport_middleware.py +122 -0
- mcp_proxy_adapter/api/middleware/unified_security.py +197 -0
- mcp_proxy_adapter/api/middleware/user_info_middleware.py +158 -0
- mcp_proxy_adapter/commands/__init__.py +19 -4
- mcp_proxy_adapter/commands/auth_validation_command.py +408 -0
- mcp_proxy_adapter/commands/base.py +66 -32
- mcp_proxy_adapter/commands/builtin_commands.py +95 -0
- mcp_proxy_adapter/commands/catalog_manager.py +838 -0
- mcp_proxy_adapter/commands/cert_monitor_command.py +620 -0
- mcp_proxy_adapter/commands/certificate_management_command.py +608 -0
- mcp_proxy_adapter/commands/command_registry.py +711 -354
- mcp_proxy_adapter/commands/dependency_manager.py +245 -0
- mcp_proxy_adapter/commands/echo_command.py +81 -0
- mcp_proxy_adapter/commands/health_command.py +8 -1
- mcp_proxy_adapter/commands/help_command.py +21 -14
- mcp_proxy_adapter/commands/hooks.py +200 -167
- mcp_proxy_adapter/commands/key_management_command.py +506 -0
- mcp_proxy_adapter/commands/load_command.py +176 -0
- mcp_proxy_adapter/commands/plugins_command.py +235 -0
- mcp_proxy_adapter/commands/protocol_management_command.py +232 -0
- mcp_proxy_adapter/commands/proxy_registration_command.py +409 -0
- mcp_proxy_adapter/commands/reload_command.py +48 -50
- mcp_proxy_adapter/commands/result.py +1 -0
- mcp_proxy_adapter/commands/role_test_command.py +141 -0
- mcp_proxy_adapter/commands/roles_management_command.py +697 -0
- mcp_proxy_adapter/commands/security_command.py +488 -0
- mcp_proxy_adapter/commands/ssl_setup_command.py +366 -0
- mcp_proxy_adapter/commands/token_management_command.py +529 -0
- mcp_proxy_adapter/commands/transport_management_command.py +144 -0
- mcp_proxy_adapter/commands/unload_command.py +158 -0
- mcp_proxy_adapter/config.py +394 -14
- mcp_proxy_adapter/core/app_factory.py +410 -0
- mcp_proxy_adapter/core/app_runner.py +272 -0
- mcp_proxy_adapter/core/auth_validator.py +606 -0
- mcp_proxy_adapter/core/certificate_utils.py +1045 -0
- mcp_proxy_adapter/core/client.py +574 -0
- mcp_proxy_adapter/core/client_manager.py +284 -0
- mcp_proxy_adapter/core/client_security.py +384 -0
- mcp_proxy_adapter/core/config_converter.py +405 -0
- mcp_proxy_adapter/core/config_validator.py +218 -0
- mcp_proxy_adapter/core/logging.py +19 -3
- mcp_proxy_adapter/core/mtls_asgi.py +156 -0
- mcp_proxy_adapter/core/mtls_asgi_app.py +187 -0
- mcp_proxy_adapter/core/protocol_manager.py +385 -0
- mcp_proxy_adapter/core/proxy_client.py +602 -0
- mcp_proxy_adapter/core/proxy_registration.py +522 -0
- mcp_proxy_adapter/core/role_utils.py +426 -0
- mcp_proxy_adapter/core/security_adapter.py +370 -0
- mcp_proxy_adapter/core/security_factory.py +239 -0
- mcp_proxy_adapter/core/security_integration.py +286 -0
- mcp_proxy_adapter/core/server_adapter.py +282 -0
- mcp_proxy_adapter/core/server_engine.py +270 -0
- mcp_proxy_adapter/core/settings.py +1 -0
- mcp_proxy_adapter/core/ssl_utils.py +234 -0
- mcp_proxy_adapter/core/transport_manager.py +292 -0
- mcp_proxy_adapter/core/unified_config_adapter.py +579 -0
- mcp_proxy_adapter/custom_openapi.py +22 -11
- mcp_proxy_adapter/examples/__init__.py +13 -4
- mcp_proxy_adapter/examples/basic_framework/__init__.py +9 -0
- mcp_proxy_adapter/examples/basic_framework/commands/__init__.py +4 -0
- mcp_proxy_adapter/examples/basic_framework/hooks/__init__.py +4 -0
- mcp_proxy_adapter/examples/basic_framework/main.py +44 -0
- mcp_proxy_adapter/examples/commands/__init__.py +5 -0
- mcp_proxy_adapter/examples/create_certificates_simple.py +550 -0
- mcp_proxy_adapter/examples/debug_request_state.py +112 -0
- mcp_proxy_adapter/examples/debug_role_chain.py +158 -0
- mcp_proxy_adapter/examples/demo_client.py +275 -0
- mcp_proxy_adapter/examples/examples/basic_framework/__init__.py +9 -0
- mcp_proxy_adapter/examples/examples/basic_framework/commands/__init__.py +4 -0
- mcp_proxy_adapter/examples/examples/basic_framework/hooks/__init__.py +4 -0
- mcp_proxy_adapter/examples/examples/basic_framework/main.py +44 -0
- mcp_proxy_adapter/examples/examples/full_application/__init__.py +12 -0
- mcp_proxy_adapter/examples/examples/full_application/commands/__init__.py +7 -0
- mcp_proxy_adapter/examples/examples/full_application/commands/custom_echo_command.py +80 -0
- mcp_proxy_adapter/examples/examples/full_application/commands/dynamic_calculator_command.py +90 -0
- mcp_proxy_adapter/examples/examples/full_application/hooks/__init__.py +7 -0
- mcp_proxy_adapter/examples/examples/full_application/hooks/application_hooks.py +75 -0
- mcp_proxy_adapter/examples/examples/full_application/hooks/builtin_command_hooks.py +71 -0
- mcp_proxy_adapter/examples/examples/full_application/main.py +173 -0
- mcp_proxy_adapter/examples/examples/full_application/proxy_endpoints.py +154 -0
- mcp_proxy_adapter/examples/full_application/__init__.py +12 -0
- mcp_proxy_adapter/examples/full_application/commands/__init__.py +7 -0
- mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +80 -0
- mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +90 -0
- mcp_proxy_adapter/examples/full_application/hooks/__init__.py +7 -0
- mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +75 -0
- mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +71 -0
- mcp_proxy_adapter/examples/full_application/main.py +173 -0
- mcp_proxy_adapter/examples/full_application/proxy_endpoints.py +154 -0
- mcp_proxy_adapter/examples/generate_all_certificates.py +362 -0
- mcp_proxy_adapter/examples/generate_certificates.py +177 -0
- mcp_proxy_adapter/examples/generate_certificates_and_tokens.py +369 -0
- mcp_proxy_adapter/examples/generate_test_configs.py +331 -0
- mcp_proxy_adapter/examples/proxy_registration_example.py +334 -0
- mcp_proxy_adapter/examples/run_example.py +59 -0
- mcp_proxy_adapter/examples/run_full_test_suite.py +318 -0
- mcp_proxy_adapter/examples/run_proxy_server.py +146 -0
- mcp_proxy_adapter/examples/run_security_tests.py +544 -0
- mcp_proxy_adapter/examples/run_security_tests_fixed.py +247 -0
- mcp_proxy_adapter/examples/scripts/config_generator.py +740 -0
- mcp_proxy_adapter/examples/scripts/create_certificates_simple.py +560 -0
- mcp_proxy_adapter/examples/scripts/generate_certificates_and_tokens.py +369 -0
- mcp_proxy_adapter/examples/security_test_client.py +782 -0
- mcp_proxy_adapter/examples/setup_test_environment.py +328 -0
- mcp_proxy_adapter/examples/test_config.py +148 -0
- mcp_proxy_adapter/examples/test_config_generator.py +86 -0
- mcp_proxy_adapter/examples/test_examples.py +281 -0
- mcp_proxy_adapter/examples/universal_client.py +620 -0
- mcp_proxy_adapter/main.py +93 -0
- mcp_proxy_adapter/utils/config_generator.py +1008 -0
- mcp_proxy_adapter/version.py +5 -2
- mcp_proxy_adapter-6.0.1.dist-info/METADATA +679 -0
- mcp_proxy_adapter-6.0.1.dist-info/RECORD +140 -0
- mcp_proxy_adapter-6.0.1.dist-info/entry_points.txt +2 -0
- {mcp_proxy_adapter-4.1.1.dist-info → mcp_proxy_adapter-6.0.1.dist-info}/licenses/LICENSE +2 -2
- mcp_proxy_adapter/api/middleware/auth.py +0 -146
- mcp_proxy_adapter/api/middleware/rate_limit.py +0 -152
- mcp_proxy_adapter/commands/reload_settings_command.py +0 -125
- mcp_proxy_adapter/examples/README.md +0 -124
- mcp_proxy_adapter/examples/basic_server/README.md +0 -60
- mcp_proxy_adapter/examples/basic_server/__init__.py +0 -7
- mcp_proxy_adapter/examples/basic_server/basic_custom_settings.json +0 -39
- mcp_proxy_adapter/examples/basic_server/config.json +0 -35
- mcp_proxy_adapter/examples/basic_server/custom_settings_example.py +0 -238
- mcp_proxy_adapter/examples/basic_server/server.py +0 -103
- mcp_proxy_adapter/examples/custom_commands/README.md +0 -127
- mcp_proxy_adapter/examples/custom_commands/__init__.py +0 -27
- mcp_proxy_adapter/examples/custom_commands/advanced_hooks.py +0 -250
- mcp_proxy_adapter/examples/custom_commands/auto_commands/__init__.py +0 -6
- mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_echo_command.py +0 -103
- mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_info_command.py +0 -111
- mcp_proxy_adapter/examples/custom_commands/config.json +0 -35
- mcp_proxy_adapter/examples/custom_commands/custom_health_command.py +0 -169
- mcp_proxy_adapter/examples/custom_commands/custom_help_command.py +0 -215
- mcp_proxy_adapter/examples/custom_commands/custom_openapi_generator.py +0 -76
- mcp_proxy_adapter/examples/custom_commands/custom_settings.json +0 -96
- mcp_proxy_adapter/examples/custom_commands/custom_settings_manager.py +0 -241
- mcp_proxy_adapter/examples/custom_commands/data_transform_command.py +0 -135
- mcp_proxy_adapter/examples/custom_commands/echo_command.py +0 -122
- mcp_proxy_adapter/examples/custom_commands/hooks.py +0 -230
- mcp_proxy_adapter/examples/custom_commands/intercept_command.py +0 -123
- mcp_proxy_adapter/examples/custom_commands/manual_echo_command.py +0 -103
- mcp_proxy_adapter/examples/custom_commands/server.py +0 -228
- mcp_proxy_adapter/examples/custom_commands/test_hooks.py +0 -176
- mcp_proxy_adapter/examples/deployment/README.md +0 -49
- mcp_proxy_adapter/examples/deployment/__init__.py +0 -7
- mcp_proxy_adapter/examples/deployment/config.development.json +0 -8
- mcp_proxy_adapter/examples/deployment/config.json +0 -29
- mcp_proxy_adapter/examples/deployment/config.production.json +0 -12
- mcp_proxy_adapter/examples/deployment/config.staging.json +0 -11
- mcp_proxy_adapter/examples/deployment/docker-compose.yml +0 -31
- mcp_proxy_adapter/examples/deployment/run.sh +0 -43
- mcp_proxy_adapter/examples/deployment/run_docker.sh +0 -84
- mcp_proxy_adapter/schemas/base_schema.json +0 -114
- mcp_proxy_adapter/schemas/openapi_schema.json +0 -314
- mcp_proxy_adapter/tests/__init__.py +0 -0
- mcp_proxy_adapter/tests/api/__init__.py +0 -3
- mcp_proxy_adapter/tests/api/test_cmd_endpoint.py +0 -115
- mcp_proxy_adapter/tests/api/test_custom_openapi.py +0 -617
- mcp_proxy_adapter/tests/api/test_handlers.py +0 -522
- mcp_proxy_adapter/tests/api/test_middleware.py +0 -340
- mcp_proxy_adapter/tests/api/test_schemas.py +0 -546
- mcp_proxy_adapter/tests/api/test_tool_integration.py +0 -531
- mcp_proxy_adapter/tests/commands/__init__.py +0 -3
- mcp_proxy_adapter/tests/commands/test_config_command.py +0 -211
- mcp_proxy_adapter/tests/commands/test_echo_command.py +0 -127
- mcp_proxy_adapter/tests/commands/test_help_command.py +0 -136
- mcp_proxy_adapter/tests/conftest.py +0 -131
- mcp_proxy_adapter/tests/functional/__init__.py +0 -3
- mcp_proxy_adapter/tests/functional/test_api.py +0 -253
- mcp_proxy_adapter/tests/integration/__init__.py +0 -3
- mcp_proxy_adapter/tests/integration/test_cmd_integration.py +0 -129
- mcp_proxy_adapter/tests/integration/test_integration.py +0 -255
- mcp_proxy_adapter/tests/performance/__init__.py +0 -3
- mcp_proxy_adapter/tests/performance/test_performance.py +0 -189
- mcp_proxy_adapter/tests/stubs/__init__.py +0 -10
- mcp_proxy_adapter/tests/stubs/echo_command.py +0 -104
- mcp_proxy_adapter/tests/test_api_endpoints.py +0 -271
- mcp_proxy_adapter/tests/test_api_handlers.py +0 -289
- mcp_proxy_adapter/tests/test_base_command.py +0 -123
- mcp_proxy_adapter/tests/test_batch_requests.py +0 -117
- mcp_proxy_adapter/tests/test_command_registry.py +0 -281
- mcp_proxy_adapter/tests/test_config.py +0 -127
- mcp_proxy_adapter/tests/test_utils.py +0 -65
- mcp_proxy_adapter/tests/unit/__init__.py +0 -3
- mcp_proxy_adapter/tests/unit/test_base_command.py +0 -436
- mcp_proxy_adapter/tests/unit/test_config.py +0 -217
- mcp_proxy_adapter-4.1.1.dist-info/METADATA +0 -200
- mcp_proxy_adapter-4.1.1.dist-info/RECORD +0 -110
- {mcp_proxy_adapter-4.1.1.dist-info → mcp_proxy_adapter-6.0.1.dist-info}/WHEEL +0 -0
- {mcp_proxy_adapter-4.1.1.dist-info → mcp_proxy_adapter-6.0.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,270 @@
|
|
1
|
+
"""
|
2
|
+
Server Engine Abstraction
|
3
|
+
|
4
|
+
This module provides an abstraction layer for the hypercorn ASGI server engine,
|
5
|
+
providing full mTLS support and SSL capabilities.
|
6
|
+
|
7
|
+
Author: Vasiliy Zdanovskiy
|
8
|
+
email: vasilyvz@gmail.com
|
9
|
+
"""
|
10
|
+
|
11
|
+
import logging
|
12
|
+
from abc import ABC, abstractmethod
|
13
|
+
from typing import Dict, Any, Optional
|
14
|
+
from pathlib import Path
|
15
|
+
|
16
|
+
logger = logging.getLogger(__name__)
|
17
|
+
|
18
|
+
|
19
|
+
class ServerEngine(ABC):
|
20
|
+
"""
|
21
|
+
Abstract base class for server engines.
|
22
|
+
|
23
|
+
This class defines the interface that all server engines must implement,
|
24
|
+
allowing the framework to work with different ASGI servers transparently.
|
25
|
+
"""
|
26
|
+
|
27
|
+
@abstractmethod
|
28
|
+
def get_name(self) -> str:
|
29
|
+
"""Get the name of the server engine."""
|
30
|
+
pass
|
31
|
+
|
32
|
+
@abstractmethod
|
33
|
+
def get_supported_features(self) -> Dict[str, bool]:
|
34
|
+
"""
|
35
|
+
Get supported features of this server engine.
|
36
|
+
|
37
|
+
Returns:
|
38
|
+
Dictionary mapping feature names to boolean support status
|
39
|
+
"""
|
40
|
+
pass
|
41
|
+
|
42
|
+
@abstractmethod
|
43
|
+
def get_config_schema(self) -> Dict[str, Any]:
|
44
|
+
"""
|
45
|
+
Get configuration schema for this server engine.
|
46
|
+
|
47
|
+
Returns:
|
48
|
+
Dictionary describing the configuration options
|
49
|
+
"""
|
50
|
+
pass
|
51
|
+
|
52
|
+
@abstractmethod
|
53
|
+
def validate_config(self, config: Dict[str, Any]) -> bool:
|
54
|
+
"""
|
55
|
+
Validate configuration for this server engine.
|
56
|
+
|
57
|
+
Args:
|
58
|
+
config: Configuration dictionary
|
59
|
+
|
60
|
+
Returns:
|
61
|
+
True if configuration is valid, False otherwise
|
62
|
+
"""
|
63
|
+
pass
|
64
|
+
|
65
|
+
@abstractmethod
|
66
|
+
def run_server(self, app: Any, config: Dict[str, Any]) -> None:
|
67
|
+
"""
|
68
|
+
Run the server with the given configuration.
|
69
|
+
|
70
|
+
Args:
|
71
|
+
app: ASGI application
|
72
|
+
config: Server configuration
|
73
|
+
"""
|
74
|
+
pass
|
75
|
+
|
76
|
+
|
77
|
+
class HypercornEngine(ServerEngine):
|
78
|
+
"""
|
79
|
+
Hypercorn server engine implementation.
|
80
|
+
|
81
|
+
Provides full mTLS support and better SSL capabilities.
|
82
|
+
"""
|
83
|
+
|
84
|
+
def get_name(self) -> str:
|
85
|
+
return "hypercorn"
|
86
|
+
|
87
|
+
def get_supported_features(self) -> Dict[str, bool]:
|
88
|
+
return {
|
89
|
+
"ssl_tls": True,
|
90
|
+
"mtls_client_certs": True, # Full support
|
91
|
+
"ssl_scope_info": True, # SSL info in request scope
|
92
|
+
"client_cert_verification": True,
|
93
|
+
"websockets": True,
|
94
|
+
"http2": True,
|
95
|
+
"reload": True
|
96
|
+
}
|
97
|
+
|
98
|
+
def get_config_schema(self) -> Dict[str, Any]:
|
99
|
+
return {
|
100
|
+
"host": {"type": "string", "default": "127.0.0.1"},
|
101
|
+
"port": {"type": "integer", "default": 8000},
|
102
|
+
"log_level": {"type": "string", "default": "INFO"},
|
103
|
+
"certfile": {"type": "string", "optional": True},
|
104
|
+
"keyfile": {"type": "string", "optional": True},
|
105
|
+
"ca_certs": {"type": "string", "optional": True},
|
106
|
+
"verify_mode": {"type": "string", "optional": True},
|
107
|
+
"reload": {"type": "boolean", "default": False},
|
108
|
+
"workers": {"type": "integer", "optional": True}
|
109
|
+
}
|
110
|
+
|
111
|
+
def validate_config(self, config: Dict[str, Any]) -> bool:
|
112
|
+
"""Validate hypercorn configuration."""
|
113
|
+
required_fields = ["host", "port"]
|
114
|
+
|
115
|
+
for field in required_fields:
|
116
|
+
if field not in config:
|
117
|
+
logger.error(f"Missing required field: {field}")
|
118
|
+
return False
|
119
|
+
|
120
|
+
# Validate SSL files exist if specified
|
121
|
+
ssl_files = ["certfile", "keyfile", "ca_certs"]
|
122
|
+
for ssl_file in ssl_files:
|
123
|
+
if ssl_file in config and config[ssl_file]:
|
124
|
+
if not Path(config[ssl_file]).exists():
|
125
|
+
logger.error(f"SSL file not found: {config[ssl_file]}")
|
126
|
+
return False
|
127
|
+
|
128
|
+
return True
|
129
|
+
|
130
|
+
def run_server(self, app: Any, config: Dict[str, Any]) -> None:
|
131
|
+
"""Run hypercorn server."""
|
132
|
+
try:
|
133
|
+
import hypercorn.asyncio
|
134
|
+
import asyncio
|
135
|
+
|
136
|
+
# Prepare hypercorn config
|
137
|
+
hypercorn_config = {
|
138
|
+
"bind": f"{config.get('host', '127.0.0.1')}:{config.get('port', 8000)}",
|
139
|
+
"log_level": config.get("log_level", "INFO"),
|
140
|
+
"reload": config.get("reload", False)
|
141
|
+
}
|
142
|
+
|
143
|
+
# Add SSL configuration if provided
|
144
|
+
logger.info(f"🔍 DEBUG: Input config keys: {list(config.keys())}")
|
145
|
+
logger.info(f"🔍 DEBUG: Input config certfile: {config.get('certfile', 'NOT_FOUND')}")
|
146
|
+
logger.info(f"🔍 DEBUG: Input config keyfile: {config.get('keyfile', 'NOT_FOUND')}")
|
147
|
+
logger.info(f"🔍 DEBUG: Input config ca_certs: {config.get('ca_certs', 'NOT_FOUND')}")
|
148
|
+
logger.info(f"🔍 DEBUG: Input config verify_mode: {config.get('verify_mode', 'NOT_FOUND')}")
|
149
|
+
|
150
|
+
if "certfile" in config and config["certfile"]:
|
151
|
+
hypercorn_config["certfile"] = config["certfile"]
|
152
|
+
if "keyfile" in config and config["keyfile"]:
|
153
|
+
hypercorn_config["keyfile"] = config["keyfile"]
|
154
|
+
if "ca_certs" in config and config["ca_certs"]:
|
155
|
+
hypercorn_config["ca_certs"] = config["ca_certs"]
|
156
|
+
if "verify_mode" in config and config["verify_mode"]:
|
157
|
+
# Convert verify_mode string to SSL constant
|
158
|
+
verify_mode_str = config["verify_mode"]
|
159
|
+
if verify_mode_str == "CERT_NONE":
|
160
|
+
import ssl
|
161
|
+
hypercorn_config["verify_mode"] = ssl.CERT_NONE
|
162
|
+
elif verify_mode_str == "CERT_REQUIRED":
|
163
|
+
import ssl
|
164
|
+
hypercorn_config["verify_mode"] = ssl.CERT_REQUIRED
|
165
|
+
elif verify_mode_str == "CERT_OPTIONAL":
|
166
|
+
import ssl
|
167
|
+
hypercorn_config["verify_mode"] = ssl.CERT_OPTIONAL
|
168
|
+
else:
|
169
|
+
hypercorn_config["verify_mode"] = verify_mode_str
|
170
|
+
|
171
|
+
# Add workers if specified
|
172
|
+
if "workers" in config and config["workers"]:
|
173
|
+
hypercorn_config["workers"] = config["workers"]
|
174
|
+
|
175
|
+
logger.info(f"Starting hypercorn server with config: {hypercorn_config}")
|
176
|
+
logger.info(f"SSL config from input: {config.get('ssl', 'NOT_FOUND')}")
|
177
|
+
logger.info(f"Security SSL config: {config.get('security', {}).get('ssl', 'NOT_FOUND')}")
|
178
|
+
logger.info(f"🔍 DEBUG: Hypercorn verify_mode: {hypercorn_config.get('verify_mode', 'NOT_SET')}")
|
179
|
+
logger.info(f"🔍 DEBUG: Hypercorn ca_certs: {hypercorn_config.get('ca_certs', 'NOT_SET')}")
|
180
|
+
|
181
|
+
# Create config object
|
182
|
+
config_obj = hypercorn.Config()
|
183
|
+
for key, value in hypercorn_config.items():
|
184
|
+
setattr(config_obj, key, value)
|
185
|
+
|
186
|
+
# Run server
|
187
|
+
asyncio.run(hypercorn.asyncio.serve(app, config_obj))
|
188
|
+
|
189
|
+
except ImportError:
|
190
|
+
logger.error("hypercorn not installed. Install with: pip install hypercorn")
|
191
|
+
raise
|
192
|
+
except Exception as e:
|
193
|
+
logger.error(f"Failed to start hypercorn server: {e}")
|
194
|
+
raise
|
195
|
+
|
196
|
+
|
197
|
+
class ServerEngineFactory:
|
198
|
+
"""
|
199
|
+
Factory for creating server engines.
|
200
|
+
|
201
|
+
This class manages the creation and configuration of different server engines.
|
202
|
+
"""
|
203
|
+
|
204
|
+
_engines: Dict[str, ServerEngine] = {}
|
205
|
+
|
206
|
+
@classmethod
|
207
|
+
def register_engine(cls, engine: ServerEngine) -> None:
|
208
|
+
"""
|
209
|
+
Register a server engine.
|
210
|
+
|
211
|
+
Args:
|
212
|
+
engine: Server engine instance to register
|
213
|
+
"""
|
214
|
+
cls._engines[engine.get_name()] = engine
|
215
|
+
logger.info(f"Registered server engine: {engine.get_name()}")
|
216
|
+
|
217
|
+
@classmethod
|
218
|
+
def get_engine(cls, name: str) -> Optional[ServerEngine]:
|
219
|
+
"""
|
220
|
+
Get a server engine by name.
|
221
|
+
|
222
|
+
Args:
|
223
|
+
name: Name of the server engine
|
224
|
+
|
225
|
+
Returns:
|
226
|
+
Server engine instance or None if not found
|
227
|
+
"""
|
228
|
+
return cls._engines.get(name)
|
229
|
+
|
230
|
+
@classmethod
|
231
|
+
def get_available_engines(cls) -> Dict[str, ServerEngine]:
|
232
|
+
"""
|
233
|
+
Get all available server engines.
|
234
|
+
|
235
|
+
Returns:
|
236
|
+
Dictionary mapping engine names to engine instances
|
237
|
+
"""
|
238
|
+
return cls._engines.copy()
|
239
|
+
|
240
|
+
@classmethod
|
241
|
+
def get_engine_with_feature(cls, feature: str) -> Optional[ServerEngine]:
|
242
|
+
"""
|
243
|
+
Get the first available engine that supports a specific feature.
|
244
|
+
|
245
|
+
Args:
|
246
|
+
feature: Name of the feature to check
|
247
|
+
|
248
|
+
Returns:
|
249
|
+
Server engine that supports the feature or None
|
250
|
+
"""
|
251
|
+
for engine in cls._engines.values():
|
252
|
+
if engine.get_supported_features().get(feature, False):
|
253
|
+
return engine
|
254
|
+
return None
|
255
|
+
|
256
|
+
@classmethod
|
257
|
+
def initialize_default_engines(cls) -> None:
|
258
|
+
"""Initialize default server engines."""
|
259
|
+
# Register hypercorn engine (only supported engine)
|
260
|
+
try:
|
261
|
+
import hypercorn
|
262
|
+
cls.register_engine(HypercornEngine())
|
263
|
+
logger.info("Hypercorn engine registered (full mTLS support available)")
|
264
|
+
except ImportError:
|
265
|
+
logger.error("Hypercorn not available - this is required for the framework")
|
266
|
+
raise
|
267
|
+
|
268
|
+
|
269
|
+
# Initialize default engines
|
270
|
+
ServerEngineFactory.initialize_default_engines()
|
@@ -117,6 +117,7 @@ class Settings:
|
|
117
117
|
return {
|
118
118
|
"auto_discovery": config.get("commands.auto_discovery", True),
|
119
119
|
"discovery_path": config.get("commands.discovery_path", "mcp_proxy_adapter.commands"),
|
120
|
+
"auto_commands_path": config.get("commands.auto_commands_path"),
|
120
121
|
"custom_commands_path": config.get("commands.custom_commands_path")
|
121
122
|
}
|
122
123
|
|
@@ -0,0 +1,234 @@
|
|
1
|
+
"""
|
2
|
+
SSL Utilities Module
|
3
|
+
|
4
|
+
This module provides utilities for SSL/TLS configuration and certificate validation.
|
5
|
+
Integrates with AuthValidator from Phase 0 for certificate validation.
|
6
|
+
|
7
|
+
Author: MCP Proxy Adapter Team
|
8
|
+
Version: 1.0.0
|
9
|
+
"""
|
10
|
+
|
11
|
+
import ssl
|
12
|
+
import logging
|
13
|
+
from typing import List, Optional, Dict, Any
|
14
|
+
from pathlib import Path
|
15
|
+
|
16
|
+
from .auth_validator import AuthValidator
|
17
|
+
|
18
|
+
logger = logging.getLogger(__name__)
|
19
|
+
|
20
|
+
|
21
|
+
class SSLUtils:
|
22
|
+
"""
|
23
|
+
SSL utilities for creating SSL contexts and validating certificates.
|
24
|
+
"""
|
25
|
+
|
26
|
+
# TLS version mapping
|
27
|
+
TLS_VERSIONS = {
|
28
|
+
"1.0": ssl.TLSVersion.TLSv1,
|
29
|
+
"1.1": ssl.TLSVersion.TLSv1_1,
|
30
|
+
"1.2": ssl.TLSVersion.TLSv1_2,
|
31
|
+
"1.3": ssl.TLSVersion.TLSv1_3
|
32
|
+
}
|
33
|
+
|
34
|
+
# Cipher suite mapping
|
35
|
+
CIPHER_SUITES = {
|
36
|
+
"TLS_AES_256_GCM_SHA384": "TLS_AES_256_GCM_SHA384",
|
37
|
+
"TLS_CHACHA20_POLY1305_SHA256": "TLS_CHACHA20_POLY1305_SHA256",
|
38
|
+
"TLS_AES_128_GCM_SHA256": "TLS_AES_128_GCM_SHA256",
|
39
|
+
"ECDHE-RSA-AES256-GCM-SHA384": "ECDHE-RSA-AES256-GCM-SHA384",
|
40
|
+
"ECDHE-RSA-AES128-GCM-SHA256": "ECDHE-RSA-AES128-GCM-SHA256",
|
41
|
+
"ECDHE-RSA-CHACHA20-POLY1305": "ECDHE-RSA-CHACHA20-POLY1305"
|
42
|
+
}
|
43
|
+
|
44
|
+
@staticmethod
|
45
|
+
def create_ssl_context(cert_file: str, key_file: str,
|
46
|
+
ca_cert: Optional[str] = None,
|
47
|
+
verify_client: bool = False,
|
48
|
+
cipher_suites: Optional[List[str]] = None,
|
49
|
+
min_tls_version: str = "1.2",
|
50
|
+
max_tls_version: str = "1.3") -> ssl.SSLContext:
|
51
|
+
"""
|
52
|
+
Create SSL context with specified configuration.
|
53
|
+
|
54
|
+
Args:
|
55
|
+
cert_file: Path to certificate file
|
56
|
+
key_file: Path to private key file
|
57
|
+
ca_cert: Path to CA certificate file (optional)
|
58
|
+
verify_client: Whether to verify client certificates
|
59
|
+
cipher_suites: List of cipher suites to use
|
60
|
+
min_tls_version: Minimum TLS version
|
61
|
+
max_tls_version: Maximum TLS version
|
62
|
+
|
63
|
+
Returns:
|
64
|
+
Configured SSL context
|
65
|
+
|
66
|
+
Raises:
|
67
|
+
ValueError: If certificate validation fails
|
68
|
+
FileNotFoundError: If certificate or key files not found
|
69
|
+
"""
|
70
|
+
# Validate certificate using AuthValidator
|
71
|
+
validator = AuthValidator()
|
72
|
+
result = validator.validate_certificate(cert_file)
|
73
|
+
if not result.is_valid:
|
74
|
+
raise ValueError(f"Invalid certificate: {result.error_message}")
|
75
|
+
|
76
|
+
# Check if files exist
|
77
|
+
if not Path(cert_file).exists():
|
78
|
+
raise FileNotFoundError(f"Certificate file not found: {cert_file}")
|
79
|
+
if not Path(key_file).exists():
|
80
|
+
raise FileNotFoundError(f"Key file not found: {key_file}")
|
81
|
+
if ca_cert and not Path(ca_cert).exists():
|
82
|
+
raise FileNotFoundError(f"CA certificate file not found: {ca_cert}")
|
83
|
+
|
84
|
+
# Create SSL context
|
85
|
+
if verify_client:
|
86
|
+
context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
|
87
|
+
else:
|
88
|
+
context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
|
89
|
+
|
90
|
+
# Load certificate and key
|
91
|
+
context.load_cert_chain(cert_file, key_file)
|
92
|
+
|
93
|
+
# Load CA certificate if provided
|
94
|
+
if ca_cert:
|
95
|
+
context.load_verify_locations(ca_cert)
|
96
|
+
|
97
|
+
# Configure client verification
|
98
|
+
if verify_client:
|
99
|
+
context.verify_mode = ssl.CERT_REQUIRED
|
100
|
+
context.check_hostname = False
|
101
|
+
else:
|
102
|
+
context.verify_mode = ssl.CERT_NONE
|
103
|
+
|
104
|
+
# Setup cipher suites
|
105
|
+
SSLUtils.setup_cipher_suites(context, cipher_suites or [])
|
106
|
+
|
107
|
+
# Setup TLS versions
|
108
|
+
SSLUtils.setup_tls_versions(context, min_tls_version, max_tls_version)
|
109
|
+
|
110
|
+
logger.info(f"SSL context created successfully with cert: {cert_file}")
|
111
|
+
return context
|
112
|
+
|
113
|
+
@staticmethod
|
114
|
+
def validate_certificate(cert_file: str) -> bool:
|
115
|
+
"""
|
116
|
+
Validate certificate using AuthValidator.
|
117
|
+
|
118
|
+
Args:
|
119
|
+
cert_file: Path to certificate file
|
120
|
+
|
121
|
+
Returns:
|
122
|
+
True if certificate is valid, False otherwise
|
123
|
+
"""
|
124
|
+
try:
|
125
|
+
validator = AuthValidator()
|
126
|
+
result = validator.validate_certificate(cert_file)
|
127
|
+
return result.is_valid
|
128
|
+
except Exception as e:
|
129
|
+
logger.error(f"Certificate validation failed: {e}")
|
130
|
+
return False
|
131
|
+
|
132
|
+
@staticmethod
|
133
|
+
def setup_cipher_suites(context: ssl.SSLContext, cipher_suites: List[str]) -> None:
|
134
|
+
"""
|
135
|
+
Setup cipher suites for SSL context.
|
136
|
+
|
137
|
+
Args:
|
138
|
+
context: SSL context to configure
|
139
|
+
cipher_suites: List of cipher suite names
|
140
|
+
"""
|
141
|
+
if not cipher_suites:
|
142
|
+
return
|
143
|
+
|
144
|
+
# Convert cipher suite names to actual cipher suite strings
|
145
|
+
actual_ciphers = []
|
146
|
+
for cipher_name in cipher_suites:
|
147
|
+
if cipher_name in SSLUtils.CIPHER_SUITES:
|
148
|
+
actual_ciphers.append(SSLUtils.CIPHER_SUITES[cipher_name])
|
149
|
+
else:
|
150
|
+
logger.warning(f"Unknown cipher suite: {cipher_name}")
|
151
|
+
|
152
|
+
if actual_ciphers:
|
153
|
+
try:
|
154
|
+
context.set_ciphers(":".join(actual_ciphers))
|
155
|
+
logger.info(f"Cipher suites configured: {actual_ciphers}")
|
156
|
+
except ssl.SSLError as e:
|
157
|
+
logger.error(f"Failed to set cipher suites: {e}")
|
158
|
+
|
159
|
+
@staticmethod
|
160
|
+
def setup_tls_versions(context: ssl.SSLContext, min_version: str, max_version: str) -> None:
|
161
|
+
"""
|
162
|
+
Setup TLS version range for SSL context.
|
163
|
+
|
164
|
+
Args:
|
165
|
+
context: SSL context to configure
|
166
|
+
min_version: Minimum TLS version
|
167
|
+
max_version: Maximum TLS version
|
168
|
+
"""
|
169
|
+
try:
|
170
|
+
min_tls = SSLUtils.TLS_VERSIONS.get(min_version)
|
171
|
+
max_tls = SSLUtils.TLS_VERSIONS.get(max_version)
|
172
|
+
|
173
|
+
if min_tls and max_tls:
|
174
|
+
context.minimum_version = min_tls
|
175
|
+
context.maximum_version = max_tls
|
176
|
+
logger.info(f"TLS versions configured: {min_version} - {max_version}")
|
177
|
+
else:
|
178
|
+
logger.warning(f"Invalid TLS version range: {min_version} - {max_version}")
|
179
|
+
except Exception as e:
|
180
|
+
logger.error(f"Failed to set TLS versions: {e}")
|
181
|
+
|
182
|
+
@staticmethod
|
183
|
+
def check_tls_version(min_version: str, max_version: str) -> bool:
|
184
|
+
"""
|
185
|
+
Check if TLS version range is valid.
|
186
|
+
|
187
|
+
Args:
|
188
|
+
min_version: Minimum TLS version
|
189
|
+
max_version: Maximum TLS version
|
190
|
+
|
191
|
+
Returns:
|
192
|
+
True if version range is valid, False otherwise
|
193
|
+
"""
|
194
|
+
min_tls = SSLUtils.TLS_VERSIONS.get(min_version)
|
195
|
+
max_tls = SSLUtils.TLS_VERSIONS.get(max_version)
|
196
|
+
|
197
|
+
if not min_tls or not max_tls:
|
198
|
+
return False
|
199
|
+
|
200
|
+
# Check if min version is less than or equal to max version
|
201
|
+
return min_tls <= max_tls
|
202
|
+
|
203
|
+
@staticmethod
|
204
|
+
def get_ssl_config_for_hypercorn(ssl_config: Dict[str, Any]) -> Dict[str, Any]:
|
205
|
+
"""
|
206
|
+
Get SSL configuration for hypercorn from transport configuration.
|
207
|
+
|
208
|
+
Args:
|
209
|
+
ssl_config: SSL configuration from transport manager
|
210
|
+
|
211
|
+
Returns:
|
212
|
+
Configuration for hypercorn
|
213
|
+
"""
|
214
|
+
hypercorn_ssl = {}
|
215
|
+
|
216
|
+
if not ssl_config:
|
217
|
+
return hypercorn_ssl
|
218
|
+
|
219
|
+
# Basic SSL parameters
|
220
|
+
if ssl_config.get("cert_file"):
|
221
|
+
hypercorn_ssl["certfile"] = ssl_config["cert_file"]
|
222
|
+
|
223
|
+
if ssl_config.get("key_file"):
|
224
|
+
hypercorn_ssl["keyfile"] = ssl_config["key_file"]
|
225
|
+
|
226
|
+
if ssl_config.get("ca_cert"):
|
227
|
+
hypercorn_ssl["ca_certs"] = ssl_config["ca_cert"]
|
228
|
+
|
229
|
+
# Client verification mode
|
230
|
+
if ssl_config.get("verify_client", False):
|
231
|
+
hypercorn_ssl["verify_mode"] = "CERT_REQUIRED"
|
232
|
+
|
233
|
+
logger.info(f"Generated hypercorn SSL config: {hypercorn_ssl}")
|
234
|
+
return hypercorn_ssl
|