mcp-proxy-adapter 4.1.0__py3-none-any.whl → 6.0.0__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 +12 -0
- mcp_proxy_adapter/api/app.py +138 -11
- mcp_proxy_adapter/api/handlers.py +16 -1
- mcp_proxy_adapter/api/middleware/__init__.py +30 -29
- mcp_proxy_adapter/api/middleware/auth_adapter.py +235 -0
- mcp_proxy_adapter/api/middleware/error_handling.py +9 -0
- mcp_proxy_adapter/api/middleware/factory.py +219 -0
- mcp_proxy_adapter/api/middleware/logging.py +32 -6
- mcp_proxy_adapter/api/middleware/mtls_adapter.py +305 -0
- mcp_proxy_adapter/api/middleware/mtls_middleware.py +296 -0
- mcp_proxy_adapter/api/middleware/protocol_middleware.py +135 -0
- mcp_proxy_adapter/api/middleware/rate_limit_adapter.py +241 -0
- mcp_proxy_adapter/api/middleware/roles_adapter.py +365 -0
- mcp_proxy_adapter/api/middleware/roles_middleware.py +381 -0
- mcp_proxy_adapter/api/middleware/security.py +376 -0
- mcp_proxy_adapter/api/middleware/token_auth_middleware.py +261 -0
- mcp_proxy_adapter/api/middleware/transport_middleware.py +122 -0
- mcp_proxy_adapter/commands/__init__.py +13 -4
- mcp_proxy_adapter/commands/auth_validation_command.py +408 -0
- mcp_proxy_adapter/commands/base.py +61 -30
- mcp_proxy_adapter/commands/builtin_commands.py +89 -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 +705 -345
- mcp_proxy_adapter/commands/dependency_manager.py +245 -0
- mcp_proxy_adapter/commands/health_command.py +7 -0
- 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 +268 -0
- mcp_proxy_adapter/commands/reload_command.py +48 -50
- mcp_proxy_adapter/commands/result.py +1 -0
- mcp_proxy_adapter/commands/roles_management_command.py +697 -0
- mcp_proxy_adapter/commands/ssl_setup_command.py +483 -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 +99 -2
- mcp_proxy_adapter/core/auth_validator.py +606 -0
- mcp_proxy_adapter/core/certificate_utils.py +827 -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 +11 -0
- mcp_proxy_adapter/core/protocol_manager.py +226 -0
- mcp_proxy_adapter/core/proxy_registration.py +270 -0
- mcp_proxy_adapter/core/role_utils.py +426 -0
- mcp_proxy_adapter/core/security_adapter.py +373 -0
- mcp_proxy_adapter/core/security_factory.py +239 -0
- mcp_proxy_adapter/core/settings.py +1 -0
- mcp_proxy_adapter/core/ssl_utils.py +233 -0
- mcp_proxy_adapter/core/transport_manager.py +292 -0
- mcp_proxy_adapter/custom_openapi.py +22 -11
- mcp_proxy_adapter/examples/basic_server/config.json +58 -23
- mcp_proxy_adapter/examples/basic_server/config_all_protocols.json +54 -0
- mcp_proxy_adapter/examples/basic_server/config_http.json +70 -0
- mcp_proxy_adapter/examples/basic_server/config_http_only.json +52 -0
- mcp_proxy_adapter/examples/basic_server/config_https.json +58 -0
- mcp_proxy_adapter/examples/basic_server/config_mtls.json +58 -0
- mcp_proxy_adapter/examples/basic_server/config_ssl.json +46 -0
- mcp_proxy_adapter/examples/basic_server/server.py +17 -1
- mcp_proxy_adapter/examples/custom_commands/__init__.py +1 -1
- mcp_proxy_adapter/examples/custom_commands/advanced_hooks.py +339 -23
- mcp_proxy_adapter/examples/custom_commands/auto_commands/test_command.py +105 -0
- mcp_proxy_adapter/examples/custom_commands/catalog/commands/test_command.py +129 -0
- mcp_proxy_adapter/examples/custom_commands/config.json +97 -41
- mcp_proxy_adapter/examples/custom_commands/config_all_protocols.json +46 -0
- mcp_proxy_adapter/examples/custom_commands/config_https_only.json +46 -0
- mcp_proxy_adapter/examples/custom_commands/config_https_transport.json +33 -0
- mcp_proxy_adapter/examples/custom_commands/config_mtls_only.json +46 -0
- mcp_proxy_adapter/examples/custom_commands/config_mtls_transport.json +33 -0
- mcp_proxy_adapter/examples/custom_commands/config_single_transport.json +33 -0
- mcp_proxy_adapter/examples/custom_commands/full_help_response.json +1 -0
- mcp_proxy_adapter/examples/custom_commands/generated_openapi.json +629 -0
- mcp_proxy_adapter/examples/custom_commands/get_openapi.py +103 -0
- mcp_proxy_adapter/examples/custom_commands/loadable_commands/test_ignored.py +129 -0
- mcp_proxy_adapter/examples/custom_commands/proxy_connection_manager.py +278 -0
- mcp_proxy_adapter/examples/custom_commands/server.py +92 -63
- mcp_proxy_adapter/examples/custom_commands/simple_openapi_server.py +75 -0
- mcp_proxy_adapter/examples/custom_commands/start_server_with_proxy_manager.py +299 -0
- mcp_proxy_adapter/examples/custom_commands/start_server_with_registration.py +278 -0
- mcp_proxy_adapter/examples/custom_commands/test_openapi.py +27 -0
- mcp_proxy_adapter/examples/custom_commands/test_registry.py +23 -0
- mcp_proxy_adapter/examples/custom_commands/test_simple.py +19 -0
- mcp_proxy_adapter/examples/custom_project_example/README.md +103 -0
- mcp_proxy_adapter/examples/custom_project_example/README_EN.md +103 -0
- mcp_proxy_adapter/examples/simple_custom_commands/README.md +149 -0
- mcp_proxy_adapter/examples/simple_custom_commands/README_EN.md +149 -0
- mcp_proxy_adapter/main.py +175 -0
- mcp_proxy_adapter/schemas/roles_schema.json +162 -0
- mcp_proxy_adapter/tests/unit/test_config.py +53 -0
- mcp_proxy_adapter/version.py +1 -1
- {mcp_proxy_adapter-4.1.0.dist-info → mcp_proxy_adapter-6.0.0.dist-info}/METADATA +2 -1
- mcp_proxy_adapter-6.0.0.dist-info/RECORD +179 -0
- mcp_proxy_adapter/commands/reload_settings_command.py +0 -125
- mcp_proxy_adapter-4.1.0.dist-info/RECORD +0 -110
- {mcp_proxy_adapter-4.1.0.dist-info → mcp_proxy_adapter-6.0.0.dist-info}/WHEEL +0 -0
- {mcp_proxy_adapter-4.1.0.dist-info → mcp_proxy_adapter-6.0.0.dist-info}/licenses/LICENSE +0 -0
- {mcp_proxy_adapter-4.1.0.dist-info → mcp_proxy_adapter-6.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,233 @@
|
|
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_uvicorn(ssl_config: Dict[str, Any]) -> Dict[str, Any]:
|
205
|
+
"""
|
206
|
+
Get SSL configuration for uvicorn from transport configuration.
|
207
|
+
|
208
|
+
Args:
|
209
|
+
ssl_config: SSL configuration from transport manager
|
210
|
+
|
211
|
+
Returns:
|
212
|
+
Configuration for uvicorn
|
213
|
+
"""
|
214
|
+
uvicorn_ssl = {}
|
215
|
+
|
216
|
+
if not ssl_config:
|
217
|
+
return uvicorn_ssl
|
218
|
+
|
219
|
+
# Basic SSL parameters
|
220
|
+
if ssl_config.get("cert_file"):
|
221
|
+
uvicorn_ssl["ssl_certfile"] = ssl_config["cert_file"]
|
222
|
+
|
223
|
+
if ssl_config.get("key_file"):
|
224
|
+
uvicorn_ssl["ssl_keyfile"] = ssl_config["key_file"]
|
225
|
+
|
226
|
+
if ssl_config.get("ca_cert"):
|
227
|
+
uvicorn_ssl["ssl_ca_certs"] = ssl_config["ca_cert"]
|
228
|
+
|
229
|
+
# Note: uvicorn doesn't support ssl_verify_mode parameter
|
230
|
+
# Client verification is handled at the application level
|
231
|
+
|
232
|
+
logger.info(f"Generated uvicorn SSL config: {uvicorn_ssl}")
|
233
|
+
return uvicorn_ssl
|
@@ -0,0 +1,292 @@
|
|
1
|
+
"""
|
2
|
+
Transport manager module.
|
3
|
+
|
4
|
+
This module provides transport management functionality for the MCP Proxy Adapter.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from typing import Dict, Any, Optional
|
8
|
+
from dataclasses import dataclass
|
9
|
+
from enum import Enum
|
10
|
+
from pathlib import Path
|
11
|
+
|
12
|
+
from mcp_proxy_adapter.core.logging import logger
|
13
|
+
|
14
|
+
|
15
|
+
class TransportType(Enum):
|
16
|
+
"""Transport types enumeration."""
|
17
|
+
HTTP = "http"
|
18
|
+
HTTPS = "https"
|
19
|
+
MTLS = "mtls"
|
20
|
+
|
21
|
+
|
22
|
+
@dataclass
|
23
|
+
class TransportConfig:
|
24
|
+
"""Transport configuration data class."""
|
25
|
+
type: TransportType
|
26
|
+
port: Optional[int]
|
27
|
+
ssl_enabled: bool
|
28
|
+
cert_file: Optional[str]
|
29
|
+
key_file: Optional[str]
|
30
|
+
ca_cert: Optional[str]
|
31
|
+
verify_client: bool
|
32
|
+
client_cert_required: bool
|
33
|
+
|
34
|
+
|
35
|
+
class TransportManager:
|
36
|
+
"""
|
37
|
+
Transport manager for handling different transport types.
|
38
|
+
|
39
|
+
This class manages transport configuration and provides utilities
|
40
|
+
for determining ports and SSL settings based on transport type.
|
41
|
+
"""
|
42
|
+
|
43
|
+
# Default ports for transport types
|
44
|
+
DEFAULT_PORTS = {
|
45
|
+
TransportType.HTTP: 8000,
|
46
|
+
TransportType.HTTPS: 8443,
|
47
|
+
TransportType.MTLS: 9443
|
48
|
+
}
|
49
|
+
|
50
|
+
def __init__(self):
|
51
|
+
"""Initialize transport manager."""
|
52
|
+
self._config: Optional[TransportConfig] = None
|
53
|
+
self._current_transport: Optional[TransportType] = None
|
54
|
+
|
55
|
+
def load_config(self, config: Dict[str, Any]) -> bool:
|
56
|
+
"""
|
57
|
+
Load transport configuration from config dict.
|
58
|
+
|
59
|
+
Args:
|
60
|
+
config: Configuration dictionary
|
61
|
+
|
62
|
+
Returns:
|
63
|
+
True if config loaded successfully, False otherwise
|
64
|
+
"""
|
65
|
+
try:
|
66
|
+
transport_config = config.get("transport", {})
|
67
|
+
|
68
|
+
# Get transport type
|
69
|
+
transport_type_str = transport_config.get("type", "http").lower()
|
70
|
+
try:
|
71
|
+
transport_type = TransportType(transport_type_str)
|
72
|
+
except ValueError:
|
73
|
+
logger.error(f"Invalid transport type: {transport_type_str}")
|
74
|
+
return False
|
75
|
+
|
76
|
+
# Get port (use default if not specified)
|
77
|
+
port = transport_config.get("port")
|
78
|
+
if port is None:
|
79
|
+
port = self.DEFAULT_PORTS.get(transport_type, 8000)
|
80
|
+
|
81
|
+
# Get SSL configuration
|
82
|
+
ssl_config = transport_config.get("ssl", {})
|
83
|
+
ssl_enabled = ssl_config.get("enabled", False)
|
84
|
+
|
85
|
+
# Validate SSL requirements
|
86
|
+
if transport_type in [TransportType.HTTPS, TransportType.MTLS] and not ssl_enabled:
|
87
|
+
logger.error(f"SSL must be enabled for transport type: {transport_type.value}")
|
88
|
+
return False
|
89
|
+
|
90
|
+
if transport_type == TransportType.HTTP and ssl_enabled:
|
91
|
+
logger.warning("SSL enabled for HTTP transport - this may cause issues")
|
92
|
+
|
93
|
+
# Create transport config
|
94
|
+
self._config = TransportConfig(
|
95
|
+
type=transport_type,
|
96
|
+
port=port,
|
97
|
+
ssl_enabled=ssl_enabled,
|
98
|
+
cert_file=ssl_config.get("cert_file") if ssl_enabled else None,
|
99
|
+
key_file=ssl_config.get("key_file") if ssl_enabled else None,
|
100
|
+
ca_cert=ssl_config.get("ca_cert") if ssl_enabled else None,
|
101
|
+
verify_client=ssl_config.get("verify_client", False),
|
102
|
+
client_cert_required=ssl_config.get("client_cert_required", False)
|
103
|
+
)
|
104
|
+
|
105
|
+
self._current_transport = transport_type
|
106
|
+
|
107
|
+
logger.info(f"Transport config loaded: {transport_type.value} on port {port}")
|
108
|
+
return True
|
109
|
+
|
110
|
+
except Exception as e:
|
111
|
+
logger.error(f"Failed to load transport config: {e}")
|
112
|
+
return False
|
113
|
+
|
114
|
+
def get_transport_type(self) -> Optional[TransportType]:
|
115
|
+
"""
|
116
|
+
Get current transport type.
|
117
|
+
|
118
|
+
Returns:
|
119
|
+
Current transport type or None if not configured
|
120
|
+
"""
|
121
|
+
return self._current_transport
|
122
|
+
|
123
|
+
def get_port(self) -> Optional[int]:
|
124
|
+
"""
|
125
|
+
Get configured port.
|
126
|
+
|
127
|
+
Returns:
|
128
|
+
Port number or None if not configured
|
129
|
+
"""
|
130
|
+
return self._config.port if self._config else None
|
131
|
+
|
132
|
+
def is_ssl_enabled(self) -> bool:
|
133
|
+
"""
|
134
|
+
Check if SSL is enabled.
|
135
|
+
|
136
|
+
Returns:
|
137
|
+
True if SSL is enabled, False otherwise
|
138
|
+
"""
|
139
|
+
return self._config.ssl_enabled if self._config else False
|
140
|
+
|
141
|
+
def get_ssl_config(self) -> Optional[Dict[str, Any]]:
|
142
|
+
"""
|
143
|
+
Get SSL configuration.
|
144
|
+
|
145
|
+
Returns:
|
146
|
+
SSL configuration dict or None if SSL not enabled
|
147
|
+
"""
|
148
|
+
if not self._config or not self._config.ssl_enabled:
|
149
|
+
return None
|
150
|
+
|
151
|
+
return {
|
152
|
+
"cert_file": self._config.cert_file,
|
153
|
+
"key_file": self._config.key_file,
|
154
|
+
"ca_cert": self._config.ca_cert,
|
155
|
+
"verify_client": self._config.verify_client,
|
156
|
+
"client_cert_required": self._config.client_cert_required
|
157
|
+
}
|
158
|
+
|
159
|
+
def is_mtls(self) -> bool:
|
160
|
+
"""
|
161
|
+
Check if current transport is MTLS.
|
162
|
+
|
163
|
+
Returns:
|
164
|
+
True if MTLS transport, False otherwise
|
165
|
+
"""
|
166
|
+
return self._current_transport == TransportType.MTLS
|
167
|
+
|
168
|
+
def is_https(self) -> bool:
|
169
|
+
"""
|
170
|
+
Check if current transport is HTTPS.
|
171
|
+
|
172
|
+
Returns:
|
173
|
+
True if HTTPS transport, False otherwise
|
174
|
+
"""
|
175
|
+
return self._current_transport == TransportType.HTTPS
|
176
|
+
|
177
|
+
def is_http(self) -> bool:
|
178
|
+
"""
|
179
|
+
Check if current transport is HTTP.
|
180
|
+
|
181
|
+
Returns:
|
182
|
+
True if HTTP transport, False otherwise
|
183
|
+
"""
|
184
|
+
return self._current_transport == TransportType.HTTP
|
185
|
+
|
186
|
+
def get_transport_info(self) -> Dict[str, Any]:
|
187
|
+
"""
|
188
|
+
Get transport information.
|
189
|
+
|
190
|
+
Returns:
|
191
|
+
Dictionary with transport information
|
192
|
+
"""
|
193
|
+
if not self._config:
|
194
|
+
return {"error": "Transport not configured"}
|
195
|
+
|
196
|
+
return {
|
197
|
+
"type": self._config.type.value,
|
198
|
+
"port": self._config.port,
|
199
|
+
"ssl_enabled": self._config.ssl_enabled,
|
200
|
+
"is_mtls": self.is_mtls(),
|
201
|
+
"is_https": self.is_https(),
|
202
|
+
"is_http": self.is_http(),
|
203
|
+
"ssl_config": self.get_ssl_config()
|
204
|
+
}
|
205
|
+
|
206
|
+
def validate_config(self) -> bool:
|
207
|
+
"""
|
208
|
+
Validate current transport configuration.
|
209
|
+
|
210
|
+
Returns:
|
211
|
+
True if configuration is valid, False otherwise
|
212
|
+
"""
|
213
|
+
if not self._config:
|
214
|
+
logger.error("Transport not configured")
|
215
|
+
return False
|
216
|
+
|
217
|
+
# Validate SSL requirements
|
218
|
+
if self._config.type in [TransportType.HTTPS, TransportType.MTLS]:
|
219
|
+
if not self._config.ssl_enabled:
|
220
|
+
logger.error(f"SSL must be enabled for {self._config.type.value}")
|
221
|
+
return False
|
222
|
+
|
223
|
+
if not self._config.cert_file or not self._config.key_file:
|
224
|
+
logger.error(f"SSL certificate and key required for {self._config.type.value}")
|
225
|
+
return False
|
226
|
+
|
227
|
+
# Validate SSL files exist
|
228
|
+
if not self.validate_ssl_files():
|
229
|
+
return False
|
230
|
+
|
231
|
+
# Validate MTLS requirements
|
232
|
+
if self._config.type == TransportType.MTLS:
|
233
|
+
if not self._config.verify_client:
|
234
|
+
logger.warning("MTLS transport should have client verification enabled")
|
235
|
+
|
236
|
+
if not self._config.ca_cert:
|
237
|
+
logger.warning("CA certificate recommended for MTLS transport")
|
238
|
+
|
239
|
+
logger.info(f"Transport configuration validated: {self._config.type.value}")
|
240
|
+
return True
|
241
|
+
|
242
|
+
def validate_ssl_files(self) -> bool:
|
243
|
+
"""
|
244
|
+
Check if SSL files exist.
|
245
|
+
|
246
|
+
Returns:
|
247
|
+
True if all SSL files exist, False otherwise
|
248
|
+
"""
|
249
|
+
if not self._config or not self._config.ssl_enabled:
|
250
|
+
return True
|
251
|
+
|
252
|
+
files_to_check = []
|
253
|
+
if self._config.cert_file:
|
254
|
+
files_to_check.append(self._config.cert_file)
|
255
|
+
if self._config.key_file:
|
256
|
+
files_to_check.append(self._config.key_file)
|
257
|
+
if self._config.ca_cert:
|
258
|
+
files_to_check.append(self._config.ca_cert)
|
259
|
+
|
260
|
+
for file_path in files_to_check:
|
261
|
+
if not Path(file_path).exists():
|
262
|
+
logger.error(f"SSL file not found: {file_path}")
|
263
|
+
return False
|
264
|
+
|
265
|
+
logger.info(f"All SSL files validated successfully: {files_to_check}")
|
266
|
+
return True
|
267
|
+
|
268
|
+
def get_uvicorn_config(self) -> Dict[str, Any]:
|
269
|
+
"""
|
270
|
+
Get configuration for uvicorn.
|
271
|
+
|
272
|
+
Returns:
|
273
|
+
Uvicorn configuration dictionary
|
274
|
+
"""
|
275
|
+
config = {
|
276
|
+
"host": "0.0.0.0", # Can be moved to settings
|
277
|
+
"port": self.get_port(),
|
278
|
+
"log_level": "info"
|
279
|
+
}
|
280
|
+
|
281
|
+
if self.is_ssl_enabled():
|
282
|
+
ssl_config = self.get_ssl_config()
|
283
|
+
if ssl_config:
|
284
|
+
from mcp_proxy_adapter.core.ssl_utils import SSLUtils
|
285
|
+
uvicorn_ssl = SSLUtils.get_ssl_config_for_uvicorn(ssl_config)
|
286
|
+
config.update(uvicorn_ssl)
|
287
|
+
|
288
|
+
return config
|
289
|
+
|
290
|
+
|
291
|
+
# Global transport manager instance
|
292
|
+
transport_manager = TransportManager()
|
@@ -96,14 +96,25 @@ class CustomOpenAPIGenerator:
|
|
96
96
|
Returns:
|
97
97
|
Dict containing the parameter schema.
|
98
98
|
"""
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
99
|
+
try:
|
100
|
+
# Get command schema
|
101
|
+
cmd_schema = cmd_class.get_schema()
|
102
|
+
|
103
|
+
# Add title and description
|
104
|
+
cmd_schema["title"] = f"Parameters for {cmd_class.name}"
|
105
|
+
cmd_schema["description"] = f"Parameters for the {cmd_class.name} command"
|
106
|
+
|
107
|
+
return cmd_schema
|
108
|
+
except Exception as e:
|
109
|
+
# Return default schema if command schema generation fails
|
110
|
+
logger.warning(f"Failed to get schema for command {cmd_class.name}: {e}")
|
111
|
+
return {
|
112
|
+
"type": "object",
|
113
|
+
"title": f"Parameters for {cmd_class.name}",
|
114
|
+
"description": f"Parameters for the {cmd_class.name} command (schema generation failed)",
|
115
|
+
"properties": {},
|
116
|
+
"additionalProperties": True
|
117
|
+
}
|
107
118
|
|
108
119
|
def generate(self, title: Optional[str] = None, description: Optional[str] = None, version: Optional[str] = None) -> Dict[str, Any]:
|
109
120
|
"""
|
@@ -262,7 +273,7 @@ class CustomOpenAPIGenerator:
|
|
262
273
|
# Add commands to schema
|
263
274
|
self._add_commands_to_schema(schema)
|
264
275
|
|
265
|
-
logger.
|
276
|
+
logger.debug(f"Generated OpenAPI schema with {len(registry.get_all_commands())} commands")
|
266
277
|
|
267
278
|
return schema
|
268
279
|
|
@@ -358,9 +369,9 @@ def custom_openapi_with_fallback(app: FastAPI) -> Dict[str, Any]:
|
|
358
369
|
# Use the first registered generator
|
359
370
|
generator_name = list(_openapi_generators.keys())[0]
|
360
371
|
generator_func = _openapi_generators[generator_name]
|
361
|
-
logger.
|
372
|
+
logger.debug(f"Using custom OpenAPI generator: {generator_name}")
|
362
373
|
return generator_func(app)
|
363
374
|
|
364
375
|
# Fall back to default generator
|
365
|
-
logger.
|
376
|
+
logger.debug("Using default OpenAPI generator")
|
366
377
|
return custom_openapi(app)
|
@@ -1,35 +1,70 @@
|
|
1
1
|
{
|
2
2
|
"server": {
|
3
|
-
"host": "
|
4
|
-
"port":
|
3
|
+
"host": "0.0.0.0",
|
4
|
+
"port": 9443,
|
5
5
|
"debug": true,
|
6
|
-
"log_level": "
|
6
|
+
"log_level": "INFO"
|
7
7
|
},
|
8
8
|
"logging": {
|
9
|
-
"level": "
|
10
|
-
"log_dir": "./logs
|
11
|
-
"
|
12
|
-
"error_log_file": "basic_server_error.log",
|
13
|
-
"access_log_file": "basic_server_access.log",
|
14
|
-
"max_file_size": "5MB",
|
15
|
-
"backup_count": 3,
|
16
|
-
"format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
17
|
-
"date_format": "%Y-%m-%d %H:%M:%S",
|
18
|
-
"console_output": true,
|
19
|
-
"file_output": true
|
9
|
+
"level": "INFO",
|
10
|
+
"log_dir": "./logs",
|
11
|
+
"console_output": true
|
20
12
|
},
|
21
13
|
"commands": {
|
22
14
|
"auto_discovery": true,
|
23
|
-
"discovery_path": "mcp_proxy_adapter.commands"
|
24
|
-
"custom_commands_path": null
|
15
|
+
"discovery_path": "mcp_proxy_adapter.commands"
|
25
16
|
},
|
26
|
-
"
|
27
|
-
"
|
28
|
-
"
|
29
|
-
"
|
30
|
-
|
31
|
-
|
32
|
-
|
17
|
+
"ssl": {
|
18
|
+
"enabled": true,
|
19
|
+
"mode": "mtls_only",
|
20
|
+
"cert_file": "/home/vasilyvz/projects/mcp_proxy_adapter/test_env/server_test-server.crt",
|
21
|
+
"key_file": "/home/vasilyvz/projects/mcp_proxy_adapter/test_env/server_test-server.key",
|
22
|
+
"ca_cert": "/home/vasilyvz/projects/mcp_proxy_adapter/test_env/ca_test-ca.crt",
|
23
|
+
"verify_client": true,
|
24
|
+
"client_cert_required": true
|
25
|
+
},
|
26
|
+
"protocols": {
|
27
|
+
"enabled": false,
|
28
|
+
"allowed_protocols": ["https", "mtls"],
|
29
|
+
"http": {
|
30
|
+
"enabled": false,
|
31
|
+
"port": 8000
|
32
|
+
},
|
33
|
+
"https": {
|
34
|
+
"enabled": true,
|
35
|
+
"port": 9443
|
36
|
+
},
|
37
|
+
"mtls": {
|
38
|
+
"enabled": true,
|
39
|
+
"port": 9443
|
33
40
|
}
|
41
|
+
},
|
42
|
+
"roles": {
|
43
|
+
"enabled": true,
|
44
|
+
"config_file": "schemas/roles_schema.json",
|
45
|
+
"default_policy": {
|
46
|
+
"deny_by_default": true,
|
47
|
+
"require_role_match": true,
|
48
|
+
"case_sensitive": false,
|
49
|
+
"allow_wildcard": true
|
50
|
+
},
|
51
|
+
"auto_load": true,
|
52
|
+
"validation_enabled": true
|
53
|
+
},
|
54
|
+
"proxy_registration": {
|
55
|
+
"enabled": true,
|
56
|
+
"proxy_url": "http://localhost:3004",
|
57
|
+
"server_id": "mcp_proxy_adapter_mtls",
|
58
|
+
"server_name": "MTLS MCP Proxy Adapter Server",
|
59
|
+
"description": "JSON-RPC API for interacting with MCP Proxy (MTLS mode)",
|
60
|
+
"registration_timeout": 30,
|
61
|
+
"retry_attempts": 3,
|
62
|
+
"retry_delay": 5,
|
63
|
+
"auto_register_on_startup": true,
|
64
|
+
"auto_unregister_on_shutdown": true
|
65
|
+
},
|
66
|
+
"custom": {
|
67
|
+
"description": "MTLS-only server example with mutual authentication",
|
68
|
+
"server_name": "MTLS MCP Proxy Adapter Server"
|
34
69
|
}
|
35
70
|
}
|