mcp-proxy-adapter 6.3.3__py3-none-any.whl → 6.3.5__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/__init__.py +9 -5
- mcp_proxy_adapter/__main__.py +1 -1
- mcp_proxy_adapter/api/app.py +227 -176
- mcp_proxy_adapter/api/handlers.py +68 -60
- mcp_proxy_adapter/api/middleware/__init__.py +7 -5
- mcp_proxy_adapter/api/middleware/base.py +19 -16
- mcp_proxy_adapter/api/middleware/command_permission_middleware.py +44 -34
- mcp_proxy_adapter/api/middleware/error_handling.py +57 -67
- mcp_proxy_adapter/api/middleware/factory.py +50 -52
- mcp_proxy_adapter/api/middleware/logging.py +46 -30
- mcp_proxy_adapter/api/middleware/performance.py +19 -16
- mcp_proxy_adapter/api/middleware/protocol_middleware.py +80 -50
- mcp_proxy_adapter/api/middleware/transport_middleware.py +26 -24
- mcp_proxy_adapter/api/middleware/unified_security.py +70 -51
- mcp_proxy_adapter/api/middleware/user_info_middleware.py +43 -34
- mcp_proxy_adapter/api/schemas.py +69 -43
- mcp_proxy_adapter/api/tool_integration.py +83 -63
- mcp_proxy_adapter/api/tools.py +60 -50
- mcp_proxy_adapter/commands/__init__.py +15 -6
- mcp_proxy_adapter/commands/auth_validation_command.py +107 -110
- mcp_proxy_adapter/commands/base.py +108 -112
- mcp_proxy_adapter/commands/builtin_commands.py +28 -18
- mcp_proxy_adapter/commands/catalog_manager.py +394 -265
- mcp_proxy_adapter/commands/cert_monitor_command.py +222 -204
- mcp_proxy_adapter/commands/certificate_management_command.py +210 -213
- mcp_proxy_adapter/commands/command_registry.py +275 -226
- mcp_proxy_adapter/commands/config_command.py +48 -33
- mcp_proxy_adapter/commands/dependency_container.py +22 -23
- mcp_proxy_adapter/commands/dependency_manager.py +65 -56
- mcp_proxy_adapter/commands/echo_command.py +15 -15
- mcp_proxy_adapter/commands/health_command.py +31 -29
- mcp_proxy_adapter/commands/help_command.py +97 -61
- mcp_proxy_adapter/commands/hooks.py +65 -49
- mcp_proxy_adapter/commands/key_management_command.py +148 -147
- mcp_proxy_adapter/commands/load_command.py +58 -40
- mcp_proxy_adapter/commands/plugins_command.py +80 -54
- mcp_proxy_adapter/commands/protocol_management_command.py +60 -48
- mcp_proxy_adapter/commands/proxy_registration_command.py +107 -115
- mcp_proxy_adapter/commands/reload_command.py +43 -37
- mcp_proxy_adapter/commands/result.py +26 -33
- mcp_proxy_adapter/commands/role_test_command.py +26 -26
- mcp_proxy_adapter/commands/roles_management_command.py +176 -173
- mcp_proxy_adapter/commands/security_command.py +134 -122
- mcp_proxy_adapter/commands/settings_command.py +47 -56
- mcp_proxy_adapter/commands/ssl_setup_command.py +109 -129
- mcp_proxy_adapter/commands/token_management_command.py +129 -158
- mcp_proxy_adapter/commands/transport_management_command.py +41 -36
- mcp_proxy_adapter/commands/unload_command.py +42 -37
- mcp_proxy_adapter/config.py +36 -35
- mcp_proxy_adapter/core/__init__.py +19 -21
- mcp_proxy_adapter/core/app_factory.py +30 -9
- mcp_proxy_adapter/core/app_runner.py +81 -64
- mcp_proxy_adapter/core/auth_validator.py +176 -182
- mcp_proxy_adapter/core/certificate_utils.py +469 -426
- mcp_proxy_adapter/core/client.py +155 -126
- mcp_proxy_adapter/core/client_manager.py +60 -54
- mcp_proxy_adapter/core/client_security.py +108 -88
- mcp_proxy_adapter/core/config_converter.py +176 -143
- mcp_proxy_adapter/core/config_validator.py +12 -4
- mcp_proxy_adapter/core/crl_utils.py +21 -7
- mcp_proxy_adapter/core/errors.py +64 -20
- mcp_proxy_adapter/core/logging.py +34 -29
- mcp_proxy_adapter/core/mtls_asgi.py +29 -25
- mcp_proxy_adapter/core/mtls_asgi_app.py +66 -54
- mcp_proxy_adapter/core/protocol_manager.py +154 -104
- mcp_proxy_adapter/core/proxy_client.py +202 -144
- mcp_proxy_adapter/core/proxy_registration.py +12 -2
- mcp_proxy_adapter/core/role_utils.py +139 -125
- mcp_proxy_adapter/core/security_adapter.py +88 -77
- mcp_proxy_adapter/core/security_factory.py +50 -44
- mcp_proxy_adapter/core/security_integration.py +72 -24
- mcp_proxy_adapter/core/server_adapter.py +68 -64
- mcp_proxy_adapter/core/server_engine.py +71 -53
- mcp_proxy_adapter/core/settings.py +68 -58
- mcp_proxy_adapter/core/ssl_utils.py +69 -56
- mcp_proxy_adapter/core/transport_manager.py +72 -60
- mcp_proxy_adapter/core/unified_config_adapter.py +201 -150
- mcp_proxy_adapter/core/utils.py +4 -2
- mcp_proxy_adapter/custom_openapi.py +107 -99
- mcp_proxy_adapter/examples/basic_framework/main.py +9 -2
- mcp_proxy_adapter/examples/commands/__init__.py +1 -1
- mcp_proxy_adapter/examples/create_certificates_simple.py +182 -71
- mcp_proxy_adapter/examples/debug_request_state.py +38 -19
- mcp_proxy_adapter/examples/debug_role_chain.py +53 -20
- mcp_proxy_adapter/examples/demo_client.py +48 -36
- mcp_proxy_adapter/examples/examples/basic_framework/main.py +9 -2
- mcp_proxy_adapter/examples/examples/full_application/__init__.py +1 -0
- mcp_proxy_adapter/examples/examples/full_application/commands/custom_echo_command.py +22 -10
- mcp_proxy_adapter/examples/examples/full_application/commands/dynamic_calculator_command.py +24 -17
- mcp_proxy_adapter/examples/examples/full_application/hooks/application_hooks.py +16 -3
- mcp_proxy_adapter/examples/examples/full_application/hooks/builtin_command_hooks.py +13 -3
- mcp_proxy_adapter/examples/examples/full_application/main.py +27 -2
- mcp_proxy_adapter/examples/examples/full_application/proxy_endpoints.py +48 -14
- mcp_proxy_adapter/examples/full_application/__init__.py +1 -0
- mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +22 -10
- mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +24 -17
- mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +16 -3
- mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +13 -3
- mcp_proxy_adapter/examples/full_application/main.py +27 -2
- mcp_proxy_adapter/examples/full_application/proxy_endpoints.py +48 -14
- mcp_proxy_adapter/examples/generate_all_certificates.py +198 -73
- mcp_proxy_adapter/examples/generate_certificates.py +31 -16
- mcp_proxy_adapter/examples/generate_certificates_and_tokens.py +220 -74
- mcp_proxy_adapter/examples/generate_test_configs.py +68 -91
- mcp_proxy_adapter/examples/proxy_registration_example.py +76 -75
- mcp_proxy_adapter/examples/run_example.py +23 -5
- mcp_proxy_adapter/examples/run_full_test_suite.py +109 -71
- mcp_proxy_adapter/examples/run_proxy_server.py +22 -9
- mcp_proxy_adapter/examples/run_security_tests.py +103 -41
- mcp_proxy_adapter/examples/run_security_tests_fixed.py +72 -36
- mcp_proxy_adapter/examples/scripts/config_generator.py +288 -187
- mcp_proxy_adapter/examples/scripts/create_certificates_simple.py +185 -72
- mcp_proxy_adapter/examples/scripts/generate_certificates_and_tokens.py +220 -74
- mcp_proxy_adapter/examples/security_test_client.py +196 -127
- mcp_proxy_adapter/examples/setup_test_environment.py +17 -29
- mcp_proxy_adapter/examples/test_config.py +19 -4
- mcp_proxy_adapter/examples/test_config_generator.py +23 -7
- mcp_proxy_adapter/examples/test_examples.py +84 -56
- mcp_proxy_adapter/examples/universal_client.py +119 -62
- mcp_proxy_adapter/openapi.py +108 -115
- mcp_proxy_adapter/utils/config_generator.py +429 -274
- mcp_proxy_adapter/version.py +1 -2
- {mcp_proxy_adapter-6.3.3.dist-info → mcp_proxy_adapter-6.3.5.dist-info}/METADATA +1 -1
- mcp_proxy_adapter-6.3.5.dist-info/RECORD +143 -0
- mcp_proxy_adapter-6.3.3.dist-info/RECORD +0 -143
- {mcp_proxy_adapter-6.3.3.dist-info → mcp_proxy_adapter-6.3.5.dist-info}/WHEEL +0 -0
- {mcp_proxy_adapter-6.3.3.dist-info → mcp_proxy_adapter-6.3.5.dist-info}/entry_points.txt +0 -0
- {mcp_proxy_adapter-6.3.3.dist-info → mcp_proxy_adapter-6.3.5.dist-info}/licenses/LICENSE +0 -0
- {mcp_proxy_adapter-6.3.3.dist-info → mcp_proxy_adapter-6.3.5.dist-info}/top_level.txt +0 -0
@@ -14,8 +14,12 @@ from pathlib import Path
|
|
14
14
|
try:
|
15
15
|
from mcp_security_framework import SecurityManager, SecurityConfig
|
16
16
|
from mcp_security_framework.schemas.config import (
|
17
|
-
AuthConfig,
|
17
|
+
AuthConfig,
|
18
|
+
SSLConfig,
|
19
|
+
PermissionConfig,
|
20
|
+
RateLimitConfig,
|
18
21
|
)
|
22
|
+
|
19
23
|
# Note: SecurityRequest and SecurityResult are not available in current version
|
20
24
|
SECURITY_FRAMEWORK_AVAILABLE = True
|
21
25
|
except ImportError:
|
@@ -34,88 +38,88 @@ from mcp_proxy_adapter.core.logging import logger
|
|
34
38
|
class SecurityAdapter:
|
35
39
|
"""
|
36
40
|
Adapter for integrating with mcp_security_framework.
|
37
|
-
|
41
|
+
|
38
42
|
Provides methods to convert mcp_proxy_adapter configuration to SecurityConfig
|
39
43
|
and handle request validation through the security framework.
|
40
44
|
"""
|
41
|
-
|
45
|
+
|
42
46
|
def __init__(self, config: Dict[str, Any]):
|
43
47
|
"""
|
44
48
|
Initialize security adapter.
|
45
|
-
|
49
|
+
|
46
50
|
Args:
|
47
51
|
config: mcp_proxy_adapter configuration dictionary
|
48
52
|
"""
|
49
53
|
self.config = config
|
50
54
|
self.security_manager = None
|
51
|
-
|
55
|
+
|
52
56
|
if SECURITY_FRAMEWORK_AVAILABLE:
|
53
57
|
self.security_manager = self._create_security_manager()
|
54
58
|
logger.info("Security adapter initialized with mcp_security_framework")
|
55
59
|
else:
|
56
60
|
logger.warning("mcp_security_framework not available, using fallback mode")
|
57
|
-
|
61
|
+
|
58
62
|
def _create_security_manager(self) -> Optional[SecurityManager]:
|
59
63
|
"""
|
60
64
|
Create SecurityManager from mcp_proxy_adapter configuration.
|
61
|
-
|
65
|
+
|
62
66
|
Returns:
|
63
67
|
SecurityManager instance or None if framework not available
|
64
68
|
"""
|
65
69
|
if not SECURITY_FRAMEWORK_AVAILABLE:
|
66
70
|
return None
|
67
|
-
|
71
|
+
|
68
72
|
try:
|
69
73
|
security_config = self._convert_config()
|
70
74
|
return SecurityManager(security_config)
|
71
75
|
except Exception as e:
|
72
76
|
logger.error(f"Failed to create SecurityManager: {e}")
|
73
77
|
return None
|
74
|
-
|
78
|
+
|
75
79
|
def _convert_config(self) -> SecurityConfig:
|
76
80
|
"""
|
77
81
|
Convert mcp_proxy_adapter configuration to SecurityConfig.
|
78
|
-
|
82
|
+
|
79
83
|
Returns:
|
80
84
|
SecurityConfig instance
|
81
85
|
"""
|
82
86
|
# Get security configuration section
|
83
87
|
security_config = self.config.get("security", {})
|
84
|
-
|
88
|
+
|
85
89
|
# Convert auth configuration
|
86
90
|
auth_config = self._convert_auth_config(security_config)
|
87
|
-
|
91
|
+
|
88
92
|
# Convert SSL configuration
|
89
93
|
ssl_config = self._convert_ssl_config(security_config)
|
90
|
-
|
94
|
+
|
91
95
|
# Convert permissions configuration
|
92
96
|
permission_config = self._convert_permission_config(security_config)
|
93
|
-
|
97
|
+
|
94
98
|
# Convert rate limit configuration
|
95
99
|
rate_limit_config = self._convert_rate_limit_config(security_config)
|
96
|
-
|
100
|
+
|
97
101
|
return SecurityConfig(
|
98
102
|
auth=auth_config,
|
99
103
|
ssl=ssl_config,
|
100
104
|
permissions=permission_config,
|
101
|
-
rate_limit=rate_limit_config
|
105
|
+
rate_limit=rate_limit_config,
|
102
106
|
)
|
103
|
-
|
107
|
+
|
104
108
|
def _convert_auth_config(self, security_config: Dict[str, Any]) -> AuthConfig:
|
105
109
|
"""
|
106
110
|
Convert authentication configuration.
|
107
|
-
|
111
|
+
|
108
112
|
Args:
|
109
113
|
security_config: Security configuration section
|
110
|
-
|
114
|
+
|
111
115
|
Returns:
|
112
116
|
AuthConfig instance
|
113
117
|
"""
|
114
118
|
auth_config = security_config.get("auth", {})
|
115
|
-
|
119
|
+
|
116
120
|
# Get authentication methods
|
117
121
|
methods = auth_config.get("methods", ["api_key"])
|
118
|
-
|
122
|
+
|
119
123
|
# Get API keys from legacy config if not in security section
|
120
124
|
api_keys = auth_config.get("api_keys", {})
|
121
125
|
if not api_keys:
|
@@ -123,31 +127,31 @@ class SecurityAdapter:
|
|
123
127
|
legacy_ssl = self.config.get("ssl", {})
|
124
128
|
if "api_keys" in legacy_ssl:
|
125
129
|
api_keys = legacy_ssl["api_keys"]
|
126
|
-
|
130
|
+
|
127
131
|
return AuthConfig(
|
128
132
|
enabled=auth_config.get("enabled", True),
|
129
133
|
methods=methods,
|
130
134
|
api_keys=api_keys,
|
131
135
|
jwt_secret=auth_config.get("jwt_secret", ""),
|
132
|
-
jwt_algorithm=auth_config.get("jwt_algorithm", "HS256")
|
136
|
+
jwt_algorithm=auth_config.get("jwt_algorithm", "HS256"),
|
133
137
|
)
|
134
|
-
|
138
|
+
|
135
139
|
def _convert_ssl_config(self, security_config: Dict[str, Any]) -> SSLConfig:
|
136
140
|
"""
|
137
141
|
Convert SSL configuration.
|
138
|
-
|
142
|
+
|
139
143
|
Args:
|
140
144
|
security_config: Security configuration section
|
141
|
-
|
145
|
+
|
142
146
|
Returns:
|
143
147
|
SSLConfig instance
|
144
148
|
"""
|
145
149
|
ssl_config = security_config.get("ssl", {})
|
146
|
-
|
150
|
+
|
147
151
|
# Fallback to legacy SSL config if not in security section
|
148
152
|
if not ssl_config:
|
149
153
|
ssl_config = self.config.get("ssl", {})
|
150
|
-
|
154
|
+
|
151
155
|
return SSLConfig(
|
152
156
|
enabled=ssl_config.get("enabled", False),
|
153
157
|
cert_file=ssl_config.get("cert_file"),
|
@@ -155,65 +159,69 @@ class SecurityAdapter:
|
|
155
159
|
ca_cert=ssl_config.get("ca_cert"),
|
156
160
|
min_tls_version=ssl_config.get("min_tls_version", "TLSv1.2"),
|
157
161
|
verify_client=ssl_config.get("verify_client", False),
|
158
|
-
client_cert_required=ssl_config.get("client_cert_required", False)
|
162
|
+
client_cert_required=ssl_config.get("client_cert_required", False),
|
159
163
|
)
|
160
|
-
|
161
|
-
def _convert_permission_config(
|
164
|
+
|
165
|
+
def _convert_permission_config(
|
166
|
+
self, security_config: Dict[str, Any]
|
167
|
+
) -> PermissionConfig:
|
162
168
|
"""
|
163
169
|
Convert permissions configuration.
|
164
|
-
|
170
|
+
|
165
171
|
Args:
|
166
172
|
security_config: Security configuration section
|
167
|
-
|
173
|
+
|
168
174
|
Returns:
|
169
175
|
PermissionConfig instance
|
170
176
|
"""
|
171
177
|
permission_config = security_config.get("permissions", {})
|
172
|
-
|
178
|
+
|
173
179
|
# Fallback to legacy roles config if not in security section
|
174
180
|
if not permission_config:
|
175
181
|
roles_config = self.config.get("roles", {})
|
176
182
|
permission_config = {
|
177
183
|
"enabled": roles_config.get("enabled", True),
|
178
184
|
"roles_file": roles_config.get("config_file", "roles.json"),
|
179
|
-
"default_role": "user"
|
185
|
+
"default_role": "user",
|
180
186
|
}
|
181
|
-
|
187
|
+
|
182
188
|
return PermissionConfig(
|
183
189
|
enabled=permission_config.get("enabled", True),
|
184
190
|
roles_file=permission_config.get("roles_file", "roles.json"),
|
185
191
|
default_role=permission_config.get("default_role", "user"),
|
186
|
-
deny_by_default=permission_config.get("deny_by_default", True)
|
192
|
+
deny_by_default=permission_config.get("deny_by_default", True),
|
187
193
|
)
|
188
|
-
|
189
|
-
def _convert_rate_limit_config(
|
194
|
+
|
195
|
+
def _convert_rate_limit_config(
|
196
|
+
self, security_config: Dict[str, Any]
|
197
|
+
) -> RateLimitConfig:
|
190
198
|
"""
|
191
199
|
Convert rate limit configuration.
|
192
|
-
|
200
|
+
|
193
201
|
Args:
|
194
202
|
security_config: Security configuration section
|
195
|
-
|
203
|
+
|
196
204
|
Returns:
|
197
205
|
RateLimitConfig instance
|
198
206
|
"""
|
199
207
|
rate_limit_config = security_config.get("rate_limit", {})
|
200
|
-
|
208
|
+
|
201
209
|
return RateLimitConfig(
|
202
210
|
enabled=rate_limit_config.get("enabled", True),
|
203
211
|
requests_per_minute=rate_limit_config.get("requests_per_minute", 60),
|
204
212
|
requests_per_hour=rate_limit_config.get("requests_per_hour", 1000),
|
205
213
|
burst_limit=rate_limit_config.get("burst_limit", 10),
|
206
214
|
by_ip=rate_limit_config.get("by_ip", True),
|
207
|
-
by_user=rate_limit_config.get("by_user", True)
|
215
|
+
by_user=rate_limit_config.get("by_user", True),
|
208
216
|
)
|
209
|
-
|
217
|
+
|
210
218
|
def validate_request(self, request_data: Dict[str, Any]) -> Dict[str, Any]:
|
211
219
|
"""
|
212
220
|
Validate request through mcp_security_framework.
|
213
|
-
|
221
|
+
|
214
222
|
Args:
|
215
223
|
request_data: Request data dictionary
|
216
|
-
|
224
|
+
|
217
225
|
Returns:
|
218
226
|
Validation result dictionary
|
219
227
|
"""
|
@@ -222,16 +230,16 @@ class SecurityAdapter:
|
|
222
230
|
# Fallback validation when framework is not available
|
223
231
|
logger.debug("Using fallback validation")
|
224
232
|
return self._fallback_validation(request_data)
|
225
|
-
|
233
|
+
|
226
234
|
try:
|
227
235
|
# Convert request data to SecurityRequest
|
228
236
|
security_request = self._create_security_request(request_data)
|
229
|
-
|
237
|
+
|
230
238
|
# Validate through security framework
|
231
239
|
result = self.security_manager.validate_request(security_request)
|
232
|
-
|
240
|
+
|
233
241
|
return result.to_dict()
|
234
|
-
|
242
|
+
|
235
243
|
except Exception as e:
|
236
244
|
logger.error(f"Security validation failed: {e}")
|
237
245
|
return {
|
@@ -239,16 +247,16 @@ class SecurityAdapter:
|
|
239
247
|
"error_code": -32603,
|
240
248
|
"error_message": f"Security validation error: {str(e)}",
|
241
249
|
"roles": [],
|
242
|
-
"user_id": None
|
250
|
+
"user_id": None,
|
243
251
|
}
|
244
|
-
|
252
|
+
|
245
253
|
def _create_security_request(self, request_data: Dict[str, Any]) -> Dict[str, Any]:
|
246
254
|
"""
|
247
255
|
Create request data for security validation.
|
248
|
-
|
256
|
+
|
249
257
|
Args:
|
250
258
|
request_data: Request data dictionary
|
251
|
-
|
259
|
+
|
252
260
|
Returns:
|
253
261
|
Request data dictionary for security validation
|
254
262
|
"""
|
@@ -258,16 +266,16 @@ class SecurityAdapter:
|
|
258
266
|
"headers": request_data.get("headers", {}),
|
259
267
|
"query_params": request_data.get("query_params", {}),
|
260
268
|
"client_ip": request_data.get("client_ip", "unknown"),
|
261
|
-
"body": request_data.get("body", {})
|
269
|
+
"body": request_data.get("body", {}),
|
262
270
|
}
|
263
|
-
|
271
|
+
|
264
272
|
def _fallback_validation(self, request_data: Dict[str, Any]) -> Dict[str, Any]:
|
265
273
|
"""
|
266
274
|
Fallback validation when mcp_security_framework is not available.
|
267
|
-
|
275
|
+
|
268
276
|
Args:
|
269
277
|
request_data: Request data dictionary
|
270
|
-
|
278
|
+
|
271
279
|
Returns:
|
272
280
|
Validation result dictionary
|
273
281
|
"""
|
@@ -275,32 +283,32 @@ class SecurityAdapter:
|
|
275
283
|
headers = request_data.get("headers", {})
|
276
284
|
query_params = request_data.get("query_params", {})
|
277
285
|
body = request_data.get("body", {})
|
278
|
-
|
286
|
+
|
279
287
|
# Check for API key in headers (FastAPI converts headers to lowercase)
|
280
288
|
api_key = headers.get("x-api-key") or headers.get("X-API-Key")
|
281
289
|
logger.debug(f"API key from headers: {api_key}")
|
282
|
-
|
290
|
+
|
283
291
|
# Check for API key in query parameters
|
284
292
|
if not api_key:
|
285
293
|
api_key = query_params.get("api_key")
|
286
294
|
logger.debug(f"API key from query params: {api_key}")
|
287
|
-
|
295
|
+
|
288
296
|
# Check for API key in JSON-RPC body
|
289
297
|
if not api_key and isinstance(body, dict):
|
290
298
|
api_key = body.get("params", {}).get("api_key")
|
291
299
|
logger.debug(f"API key from body: {api_key}")
|
292
|
-
|
300
|
+
|
293
301
|
# Get API keys from config
|
294
302
|
api_keys = self._get_api_keys()
|
295
303
|
logger.debug(f"Available API keys: {list(api_keys.keys())}")
|
296
|
-
|
304
|
+
|
297
305
|
if api_key and api_key in api_keys:
|
298
306
|
return {
|
299
307
|
"is_valid": True,
|
300
308
|
"error_code": None,
|
301
309
|
"error_message": None,
|
302
310
|
"roles": ["user"],
|
303
|
-
"user_id": api_keys[api_key]
|
311
|
+
"user_id": api_keys[api_key],
|
304
312
|
}
|
305
313
|
else:
|
306
314
|
return {
|
@@ -308,13 +316,13 @@ class SecurityAdapter:
|
|
308
316
|
"error_code": -32000,
|
309
317
|
"error_message": "API key not provided or invalid",
|
310
318
|
"roles": [],
|
311
|
-
"user_id": None
|
319
|
+
"user_id": None,
|
312
320
|
}
|
313
|
-
|
321
|
+
|
314
322
|
def _get_api_keys(self) -> Dict[str, str]:
|
315
323
|
"""
|
316
324
|
Get API keys from configuration.
|
317
|
-
|
325
|
+
|
318
326
|
Returns:
|
319
327
|
Dictionary mapping API keys to usernames
|
320
328
|
"""
|
@@ -322,48 +330,51 @@ class SecurityAdapter:
|
|
322
330
|
security_config = self.config.get("security", {})
|
323
331
|
auth_config = security_config.get("auth", {})
|
324
332
|
api_keys = auth_config.get("api_keys", {})
|
325
|
-
|
333
|
+
|
326
334
|
logger.debug(f"Security config: {security_config}")
|
327
335
|
logger.debug(f"Auth config: {auth_config}")
|
328
336
|
logger.debug(f"API keys from security config: {api_keys}")
|
329
|
-
|
337
|
+
|
330
338
|
# Fallback to legacy SSL config
|
331
339
|
if not api_keys:
|
332
340
|
ssl_config = self.config.get("ssl", {})
|
333
341
|
api_keys = ssl_config.get("api_keys", {})
|
334
342
|
logger.debug(f"API keys from SSL config: {api_keys}")
|
335
|
-
|
343
|
+
|
336
344
|
logger.info(f"Total API keys loaded: {len(api_keys)}")
|
337
345
|
return api_keys
|
338
|
-
|
346
|
+
|
339
347
|
def create_middleware(self, framework: str = "fastapi"):
|
340
348
|
"""
|
341
349
|
Create framework-specific middleware.
|
342
|
-
|
350
|
+
|
343
351
|
Args:
|
344
352
|
framework: Framework type (fastapi, flask, etc.)
|
345
|
-
|
353
|
+
|
346
354
|
Returns:
|
347
355
|
Middleware instance
|
348
356
|
"""
|
349
357
|
if not self.security_manager:
|
350
358
|
logger.warning("Cannot create middleware: security framework not available")
|
351
359
|
return None
|
352
|
-
|
360
|
+
|
353
361
|
try:
|
354
362
|
if framework == "fastapi":
|
355
|
-
from mcp_security_framework.middleware import
|
363
|
+
from mcp_security_framework.middleware import (
|
364
|
+
create_fastapi_security_middleware,
|
365
|
+
)
|
366
|
+
|
356
367
|
return create_fastapi_security_middleware(self.security_manager)
|
357
368
|
else:
|
358
369
|
raise ValueError(f"Unsupported framework: {framework}")
|
359
370
|
except Exception as e:
|
360
371
|
logger.error(f"Failed to create middleware: {e}")
|
361
372
|
return None
|
362
|
-
|
373
|
+
|
363
374
|
def is_available(self) -> bool:
|
364
375
|
"""
|
365
376
|
Check if security framework is available.
|
366
|
-
|
377
|
+
|
367
378
|
Returns:
|
368
379
|
True if framework is available, False otherwise
|
369
380
|
"""
|
@@ -15,19 +15,19 @@ from .security_adapter import SecurityAdapter
|
|
15
15
|
class SecurityFactory:
|
16
16
|
"""
|
17
17
|
Factory for creating security components.
|
18
|
-
|
18
|
+
|
19
19
|
Provides static methods to create security adapters, managers,
|
20
20
|
and middleware components with proper configuration handling.
|
21
21
|
"""
|
22
|
-
|
22
|
+
|
23
23
|
@staticmethod
|
24
24
|
def create_security_adapter(config: Dict[str, Any]) -> SecurityAdapter:
|
25
25
|
"""
|
26
26
|
Create SecurityAdapter from configuration.
|
27
|
-
|
27
|
+
|
28
28
|
Args:
|
29
29
|
config: mcp_proxy_adapter configuration dictionary
|
30
|
-
|
30
|
+
|
31
31
|
Returns:
|
32
32
|
SecurityAdapter instance
|
33
33
|
"""
|
@@ -38,15 +38,15 @@ class SecurityFactory:
|
|
38
38
|
except Exception as e:
|
39
39
|
logger.error(f"Failed to create security adapter: {e}")
|
40
40
|
raise
|
41
|
-
|
41
|
+
|
42
42
|
@staticmethod
|
43
43
|
def create_security_manager(config: Dict[str, Any]):
|
44
44
|
"""
|
45
45
|
Create SecurityManager from configuration.
|
46
|
-
|
46
|
+
|
47
47
|
Args:
|
48
48
|
config: mcp_proxy_adapter configuration dictionary
|
49
|
-
|
49
|
+
|
50
50
|
Returns:
|
51
51
|
SecurityManager instance or None if not available
|
52
52
|
"""
|
@@ -56,103 +56,103 @@ class SecurityFactory:
|
|
56
56
|
except Exception as e:
|
57
57
|
logger.error(f"Failed to create security manager: {e}")
|
58
58
|
return None
|
59
|
-
|
59
|
+
|
60
60
|
@staticmethod
|
61
61
|
def create_middleware(config: Dict[str, Any], framework: str = "fastapi"):
|
62
62
|
"""
|
63
63
|
Create framework-specific security middleware.
|
64
|
-
|
64
|
+
|
65
65
|
Args:
|
66
66
|
config: mcp_proxy_adapter configuration dictionary
|
67
67
|
framework: Framework type (fastapi, flask, etc.)
|
68
|
-
|
68
|
+
|
69
69
|
Returns:
|
70
70
|
Middleware instance or None if creation failed
|
71
71
|
"""
|
72
72
|
try:
|
73
73
|
adapter = SecurityFactory.create_security_adapter(config)
|
74
74
|
middleware = adapter.create_middleware(framework)
|
75
|
-
|
75
|
+
|
76
76
|
if middleware:
|
77
77
|
logger.info(f"Security middleware created for {framework}")
|
78
78
|
else:
|
79
79
|
logger.warning(f"Failed to create security middleware for {framework}")
|
80
|
-
|
80
|
+
|
81
81
|
return middleware
|
82
|
-
|
82
|
+
|
83
83
|
except Exception as e:
|
84
84
|
logger.error(f"Failed to create security middleware: {e}")
|
85
85
|
return None
|
86
|
-
|
86
|
+
|
87
87
|
@staticmethod
|
88
88
|
def create_fastapi_middleware(config: Dict[str, Any]):
|
89
89
|
"""
|
90
90
|
Create FastAPI-specific security middleware.
|
91
|
-
|
91
|
+
|
92
92
|
Args:
|
93
93
|
config: mcp_proxy_adapter configuration dictionary
|
94
|
-
|
94
|
+
|
95
95
|
Returns:
|
96
96
|
FastAPI middleware instance or None if creation failed
|
97
97
|
"""
|
98
98
|
return SecurityFactory.create_middleware(config, "fastapi")
|
99
|
-
|
99
|
+
|
100
100
|
@staticmethod
|
101
101
|
def validate_config(config: Dict[str, Any]) -> bool:
|
102
102
|
"""
|
103
103
|
Validate security configuration.
|
104
|
-
|
104
|
+
|
105
105
|
Args:
|
106
106
|
config: Configuration dictionary to validate
|
107
|
-
|
107
|
+
|
108
108
|
Returns:
|
109
109
|
True if configuration is valid, False otherwise
|
110
110
|
"""
|
111
111
|
try:
|
112
112
|
# Check if security section exists
|
113
113
|
security_config = config.get("security", {})
|
114
|
-
|
114
|
+
|
115
115
|
# Validate required fields
|
116
116
|
if not isinstance(security_config, dict):
|
117
117
|
logger.error("Security configuration must be a dictionary")
|
118
118
|
return False
|
119
|
-
|
119
|
+
|
120
120
|
# Validate auth configuration
|
121
121
|
auth_config = security_config.get("auth", {})
|
122
122
|
if not isinstance(auth_config, dict):
|
123
123
|
logger.error("Auth configuration must be a dictionary")
|
124
124
|
return False
|
125
|
-
|
125
|
+
|
126
126
|
# Validate SSL configuration
|
127
127
|
ssl_config = security_config.get("ssl", {})
|
128
128
|
if not isinstance(ssl_config, dict):
|
129
129
|
logger.error("SSL configuration must be a dictionary")
|
130
130
|
return False
|
131
|
-
|
131
|
+
|
132
132
|
# Validate permissions configuration
|
133
133
|
permissions_config = security_config.get("permissions", {})
|
134
134
|
if not isinstance(permissions_config, dict):
|
135
135
|
logger.error("Permissions configuration must be a dictionary")
|
136
136
|
return False
|
137
|
-
|
137
|
+
|
138
138
|
# Validate rate limit configuration
|
139
139
|
rate_limit_config = security_config.get("rate_limit", {})
|
140
140
|
if not isinstance(rate_limit_config, dict):
|
141
141
|
logger.error("Rate limit configuration must be a dictionary")
|
142
142
|
return False
|
143
|
-
|
143
|
+
|
144
144
|
logger.info("Security configuration validation passed")
|
145
145
|
return True
|
146
|
-
|
146
|
+
|
147
147
|
except Exception as e:
|
148
148
|
logger.error(f"Configuration validation failed: {e}")
|
149
149
|
return False
|
150
|
-
|
150
|
+
|
151
151
|
@staticmethod
|
152
152
|
def get_default_config() -> Dict[str, Any]:
|
153
153
|
"""
|
154
154
|
Get default security configuration.
|
155
|
-
|
155
|
+
|
156
156
|
Returns:
|
157
157
|
Default security configuration dictionary
|
158
158
|
"""
|
@@ -165,7 +165,7 @@ class SecurityFactory:
|
|
165
165
|
"methods": ["api_key"],
|
166
166
|
"api_keys": {},
|
167
167
|
"jwt_secret": "",
|
168
|
-
"jwt_algorithm": "HS256"
|
168
|
+
"jwt_algorithm": "HS256",
|
169
169
|
},
|
170
170
|
"ssl": {
|
171
171
|
"enabled": False,
|
@@ -174,13 +174,13 @@ class SecurityFactory:
|
|
174
174
|
"ca_cert": None,
|
175
175
|
"min_tls_version": "TLSv1.2",
|
176
176
|
"verify_client": False,
|
177
|
-
"client_cert_required": False
|
177
|
+
"client_cert_required": False,
|
178
178
|
},
|
179
179
|
"permissions": {
|
180
180
|
"enabled": True,
|
181
181
|
"roles_file": "roles.json",
|
182
182
|
"default_role": "user",
|
183
|
-
"deny_by_default": True
|
183
|
+
"deny_by_default": True,
|
184
184
|
},
|
185
185
|
"rate_limit": {
|
186
186
|
"enabled": True,
|
@@ -188,52 +188,58 @@ class SecurityFactory:
|
|
188
188
|
"requests_per_hour": 1000,
|
189
189
|
"burst_limit": 10,
|
190
190
|
"by_ip": True,
|
191
|
-
"by_user": True
|
192
|
-
}
|
191
|
+
"by_user": True,
|
192
|
+
},
|
193
193
|
}
|
194
194
|
}
|
195
|
-
|
195
|
+
|
196
196
|
@staticmethod
|
197
|
-
def merge_config(
|
197
|
+
def merge_config(
|
198
|
+
base_config: Dict[str, Any], security_config: Dict[str, Any]
|
199
|
+
) -> Dict[str, Any]:
|
198
200
|
"""
|
199
201
|
Merge security configuration into base configuration.
|
200
|
-
|
202
|
+
|
201
203
|
Args:
|
202
204
|
base_config: Base configuration dictionary
|
203
205
|
security_config: Security configuration to merge
|
204
|
-
|
206
|
+
|
205
207
|
Returns:
|
206
208
|
Merged configuration dictionary
|
207
209
|
"""
|
208
210
|
try:
|
209
211
|
# Create a copy of base config
|
210
212
|
merged_config = base_config.copy()
|
211
|
-
|
213
|
+
|
212
214
|
# Merge security configuration
|
213
215
|
if "security" not in merged_config:
|
214
216
|
merged_config["security"] = {}
|
215
|
-
|
217
|
+
|
216
218
|
# Deep merge security configuration
|
217
219
|
SecurityFactory._deep_merge(merged_config["security"], security_config)
|
218
|
-
|
220
|
+
|
219
221
|
logger.info("Security configuration merged successfully")
|
220
222
|
return merged_config
|
221
|
-
|
223
|
+
|
222
224
|
except Exception as e:
|
223
225
|
logger.error(f"Failed to merge security configuration: {e}")
|
224
226
|
return base_config
|
225
|
-
|
227
|
+
|
226
228
|
@staticmethod
|
227
229
|
def _deep_merge(base_dict: Dict[str, Any], update_dict: Dict[str, Any]) -> None:
|
228
230
|
"""
|
229
231
|
Deep merge two dictionaries.
|
230
|
-
|
232
|
+
|
231
233
|
Args:
|
232
234
|
base_dict: Base dictionary to update
|
233
235
|
update_dict: Dictionary with updates
|
234
236
|
"""
|
235
237
|
for key, value in update_dict.items():
|
236
|
-
if
|
238
|
+
if (
|
239
|
+
key in base_dict
|
240
|
+
and isinstance(base_dict[key], dict)
|
241
|
+
and isinstance(value, dict)
|
242
|
+
):
|
237
243
|
SecurityFactory._deep_merge(base_dict[key], value)
|
238
244
|
else:
|
239
245
|
base_dict[key] = value
|