mcp-proxy-adapter 6.3.4__py3-none-any.whl ā 6.3.6__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 +120 -91
- 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 +7 -3
- 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.4.dist-info ā mcp_proxy_adapter-6.3.6.dist-info}/METADATA +1 -1
- mcp_proxy_adapter-6.3.6.dist-info/RECORD +144 -0
- mcp_proxy_adapter-6.3.6.dist-info/top_level.txt +2 -0
- mcp_proxy_adapter_issue_package/demonstrate_issue.py +178 -0
- mcp_proxy_adapter-6.3.4.dist-info/RECORD +0 -143
- mcp_proxy_adapter-6.3.4.dist-info/top_level.txt +0 -1
- {mcp_proxy_adapter-6.3.4.dist-info ā mcp_proxy_adapter-6.3.6.dist-info}/WHEEL +0 -0
- {mcp_proxy_adapter-6.3.4.dist-info ā mcp_proxy_adapter-6.3.6.dist-info}/entry_points.txt +0 -0
- {mcp_proxy_adapter-6.3.4.dist-info ā mcp_proxy_adapter-6.3.6.dist-info}/licenses/LICENSE +0 -0
@@ -23,10 +23,17 @@ from typing import Dict, List, Optional, Any, Tuple
|
|
23
23
|
|
24
24
|
import psutil
|
25
25
|
import requests
|
26
|
+
|
26
27
|
# Import security test client with proper module path
|
27
|
-
from mcp_proxy_adapter.examples.security_test_client import
|
28
|
+
from mcp_proxy_adapter.examples.security_test_client import (
|
29
|
+
SecurityTestClient,
|
30
|
+
TestResult,
|
31
|
+
)
|
32
|
+
|
33
|
+
|
28
34
|
class SecurityTestRunner:
|
29
35
|
"""Main test runner for security testing."""
|
36
|
+
|
30
37
|
def __init__(self):
|
31
38
|
"""Initialize test runner."""
|
32
39
|
self.servers = {}
|
@@ -43,32 +50,32 @@ class SecurityTestRunner:
|
|
43
50
|
"config": "configs/http_simple.json",
|
44
51
|
"port": self.base_port + 0,
|
45
52
|
"url": f"http://localhost:{self.base_port + 0}",
|
46
|
-
"auth": "none"
|
53
|
+
"auth": "none",
|
47
54
|
},
|
48
55
|
"http_token": {
|
49
56
|
"config": "configs/http_token.json",
|
50
57
|
"port": self.base_port + 1,
|
51
58
|
"url": f"http://localhost:{self.base_port + 1}",
|
52
|
-
"auth": "api_key"
|
59
|
+
"auth": "api_key",
|
53
60
|
},
|
54
61
|
"https": {
|
55
62
|
"config": "configs/https_simple.json",
|
56
63
|
"port": self.base_port + 2,
|
57
64
|
"url": f"https://localhost:{self.base_port + 2}",
|
58
|
-
"auth": "none"
|
65
|
+
"auth": "none",
|
59
66
|
},
|
60
67
|
"https_token": {
|
61
68
|
"config": "configs/https_token.json",
|
62
69
|
"port": self.base_port + 3,
|
63
70
|
"url": f"https://localhost:{self.base_port + 3}",
|
64
|
-
"auth": "api_key"
|
71
|
+
"auth": "api_key",
|
65
72
|
},
|
66
73
|
"mtls": {
|
67
74
|
"config": "configs/mtls_no_roles.json",
|
68
75
|
"port": self.base_port + 4,
|
69
76
|
"url": f"https://localhost:{self.base_port + 4}",
|
70
|
-
"auth": "certificate"
|
71
|
-
}
|
77
|
+
"auth": "certificate",
|
78
|
+
},
|
72
79
|
}
|
73
80
|
|
74
81
|
def _port_in_use(self, port: int, host: str = "127.0.0.1") -> bool:
|
@@ -159,7 +166,11 @@ class SecurityTestRunner:
|
|
159
166
|
ssl = data.get("ssl", {})
|
160
167
|
for key in ("cert_file", "key_file", "ca_cert"):
|
161
168
|
exists, abs_path = self._validate_file(base, ssl.get(key))
|
162
|
-
if
|
169
|
+
if (
|
170
|
+
ssl.get("enabled")
|
171
|
+
and key in ("cert_file", "key_file")
|
172
|
+
and not exists
|
173
|
+
):
|
163
174
|
ok = False
|
164
175
|
missing.append(f"{name}:{key} -> {abs_path}")
|
165
176
|
sec = data.get("security", {})
|
@@ -178,20 +189,23 @@ class SecurityTestRunner:
|
|
178
189
|
else:
|
179
190
|
print("ā
Configuration file paths validated")
|
180
191
|
return ok
|
192
|
+
|
181
193
|
def check_prerequisites(self) -> bool:
|
182
194
|
"""Check if all prerequisites are met."""
|
183
195
|
print("š Checking prerequisites...")
|
184
196
|
# Check if we're in the right directory
|
185
197
|
if not Path("configs").exists():
|
186
|
-
print(
|
198
|
+
print(
|
199
|
+
"ā configs directory not found. Please run from the test environment root directory."
|
200
|
+
)
|
187
201
|
return False
|
188
202
|
# Check if certificates exist
|
189
203
|
cert_files = [
|
190
|
-
|
204
|
+
"certs/mcp_proxy_adapter_ca_ca.crt",
|
191
205
|
"certs/localhost_server.crt",
|
192
|
-
"keys/localhost_server.key"
|
206
|
+
"keys/localhost_server.key",
|
193
207
|
]
|
194
|
-
|
208
|
+
|
195
209
|
missing_certs = []
|
196
210
|
# Check if roles.json exists
|
197
211
|
roles_file = "configs/roles.json"
|
@@ -202,11 +216,16 @@ class SecurityTestRunner:
|
|
202
216
|
missing_certs.append(cert_file)
|
203
217
|
if missing_certs:
|
204
218
|
print(f"ā Missing certificates: {missing_certs}")
|
205
|
-
print(
|
219
|
+
print(
|
220
|
+
"š” Run: python -m mcp_proxy_adapter.examples.setup_test_environment to generate certificates"
|
221
|
+
)
|
206
222
|
return False
|
207
223
|
print("ā
Prerequisites check passed")
|
208
224
|
return True
|
209
|
-
|
225
|
+
|
226
|
+
def start_server(
|
227
|
+
self, name: str, config_path: str, port: int
|
228
|
+
) -> Optional[subprocess.Popen]:
|
210
229
|
"""Start a server in background."""
|
211
230
|
try:
|
212
231
|
print(f"š Starting {name} server on port {port}...")
|
@@ -222,14 +241,22 @@ class SecurityTestRunner:
|
|
222
241
|
print(f"ā Port {port} is in use, cannot start {name}")
|
223
242
|
return None
|
224
243
|
# Start server in background
|
225
|
-
logs_dir = Path("logs")
|
244
|
+
logs_dir = Path("logs")
|
245
|
+
logs_dir.mkdir(exist_ok=True)
|
226
246
|
log_path = logs_dir / f"{name}.log"
|
227
247
|
log_file = open(log_path, "wb")
|
228
248
|
self.server_logs[name] = log_file
|
229
|
-
process = subprocess.Popen(
|
230
|
-
|
231
|
-
|
232
|
-
|
249
|
+
process = subprocess.Popen(
|
250
|
+
[
|
251
|
+
sys.executable,
|
252
|
+
"-m",
|
253
|
+
"mcp_proxy_adapter.main",
|
254
|
+
"--config",
|
255
|
+
config_path,
|
256
|
+
],
|
257
|
+
stdout=log_file,
|
258
|
+
stderr=subprocess.STDOUT,
|
259
|
+
)
|
233
260
|
# Wait a bit for server to start
|
234
261
|
time.sleep(3)
|
235
262
|
# Check if process is still running
|
@@ -242,6 +269,7 @@ class SecurityTestRunner:
|
|
242
269
|
except Exception as e:
|
243
270
|
print(f"ā Error starting {name} server: {e}")
|
244
271
|
return None
|
272
|
+
|
245
273
|
def stop_server(self, name: str, process: subprocess.Popen):
|
246
274
|
"""Stop a server."""
|
247
275
|
try:
|
@@ -276,21 +304,30 @@ class SecurityTestRunner:
|
|
276
304
|
return False
|
277
305
|
|
278
306
|
# Start proxy server
|
279
|
-
cmd = [
|
280
|
-
|
307
|
+
cmd = [
|
308
|
+
sys.executable,
|
309
|
+
str(proxy_script),
|
310
|
+
"--host",
|
311
|
+
"127.0.0.1",
|
312
|
+
"--port",
|
313
|
+
str(self.proxy_port),
|
314
|
+
]
|
315
|
+
logs_dir = Path("logs")
|
316
|
+
logs_dir.mkdir(exist_ok=True)
|
281
317
|
proxy_log_path = logs_dir / "proxy_server.log"
|
282
318
|
self.proxy_log = open(proxy_log_path, "wb")
|
283
319
|
process = subprocess.Popen(
|
284
|
-
cmd,
|
285
|
-
stdout=self.proxy_log,
|
286
|
-
stderr=subprocess.STDOUT,
|
287
|
-
cwd=Path.cwd()
|
320
|
+
cmd, stdout=self.proxy_log, stderr=subprocess.STDOUT, cwd=Path.cwd()
|
288
321
|
)
|
289
322
|
|
290
323
|
# Check readiness
|
291
|
-
if process.poll() is None and self.wait_for_http(
|
324
|
+
if process.poll() is None and self.wait_for_http(
|
325
|
+
f"http://127.0.0.1:{self.proxy_port}"
|
326
|
+
):
|
292
327
|
self.proxy_server = process
|
293
|
-
print(
|
328
|
+
print(
|
329
|
+
"ā
Proxy server started successfully (PID: {})".format(process.pid)
|
330
|
+
)
|
294
331
|
return True
|
295
332
|
else:
|
296
333
|
print("ā Failed to start proxy server (see logs/proxy_server.log)")
|
@@ -304,7 +341,11 @@ class SecurityTestRunner:
|
|
304
341
|
"""Stop the proxy server."""
|
305
342
|
if self.proxy_server:
|
306
343
|
try:
|
307
|
-
print(
|
344
|
+
print(
|
345
|
+
"š Stopping proxy server (PID: {})...".format(
|
346
|
+
self.proxy_server.pid
|
347
|
+
)
|
348
|
+
)
|
308
349
|
self.proxy_server.terminate()
|
309
350
|
try:
|
310
351
|
self.proxy_server.wait(timeout=5)
|
@@ -323,6 +364,7 @@ class SecurityTestRunner:
|
|
323
364
|
self.proxy_log = None
|
324
365
|
except Exception:
|
325
366
|
pass
|
367
|
+
|
326
368
|
async def test_server(self, name: str, config: Dict[str, Any]) -> List[TestResult]:
|
327
369
|
"""Test a specific server configuration."""
|
328
370
|
print(f"\nš§Ŗ Testing {name} server...")
|
@@ -335,21 +377,18 @@ class SecurityTestRunner:
|
|
335
377
|
client.create_ssl_context = client.create_ssl_context_for_mtls
|
336
378
|
async with client as client_session:
|
337
379
|
results = await client_session.run_security_tests(
|
338
|
-
config["url"],
|
339
|
-
config["auth"]
|
380
|
+
config["url"], config["auth"]
|
340
381
|
)
|
341
382
|
else:
|
342
383
|
# For other auth types, use default SSL context
|
343
384
|
async with SecurityTestClient(config["url"]) as client:
|
344
|
-
results = await client.run_security_tests(
|
345
|
-
config["url"],
|
346
|
-
config["auth"]
|
347
|
-
)
|
385
|
+
results = await client.run_security_tests(config["url"], config["auth"])
|
348
386
|
# Print summary for this server
|
349
387
|
passed = sum(1 for r in results if r.success)
|
350
388
|
total = len(results)
|
351
389
|
print(f"\nš {name} Results: {passed}/{total} tests passed")
|
352
390
|
return results
|
391
|
+
|
353
392
|
async def run_all_tests(self) -> Dict[str, List[TestResult]]:
|
354
393
|
"""Run tests against all server configurations."""
|
355
394
|
print("š Starting comprehensive security testing")
|
@@ -385,6 +424,7 @@ class SecurityTestRunner:
|
|
385
424
|
print(f"ā ļø Skipping {name} tests (server not running)")
|
386
425
|
all_results[name] = []
|
387
426
|
return all_results
|
427
|
+
|
388
428
|
def print_final_summary(self, all_results: Dict[str, List[TestResult]]):
|
389
429
|
"""Print final test summary."""
|
390
430
|
print("\n" + "=" * 80)
|
@@ -419,11 +459,17 @@ class SecurityTestRunner:
|
|
419
459
|
print("=" * 60)
|
420
460
|
print("\nš NEXT STEPS:")
|
421
461
|
print("1. Start basic framework example:")
|
422
|
-
print(
|
462
|
+
print(
|
463
|
+
" python -m mcp_proxy_adapter.examples.basic_framework.main --config configs/https_simple.json"
|
464
|
+
)
|
423
465
|
print("\n2. Start full application example:")
|
424
|
-
print(
|
466
|
+
print(
|
467
|
+
" python -m mcp_proxy_adapter.examples.full_application.main --config configs/mtls_with_roles.json"
|
468
|
+
)
|
425
469
|
print("\n3. Test with custom configurations:")
|
426
|
-
print(
|
470
|
+
print(
|
471
|
+
" python -m mcp_proxy_adapter.examples.basic_framework.main --config configs/http_simple.json"
|
472
|
+
)
|
427
473
|
print("=" * 60)
|
428
474
|
elif total_passed > 0:
|
429
475
|
print("ā ļø SOME TESTS FAILED")
|
@@ -433,7 +479,9 @@ class SecurityTestRunner:
|
|
433
479
|
print("\n2. Check if certificates are generated:")
|
434
480
|
print(" python -m mcp_proxy_adapter.examples.generate_certificates")
|
435
481
|
print("\n3. Verify configuration files exist:")
|
436
|
-
print(
|
482
|
+
print(
|
483
|
+
" python -m mcp_proxy_adapter.examples.generate_test_configs --output-dir configs"
|
484
|
+
)
|
437
485
|
print("\n4. Check if ports are available (3004, 8000-8005)")
|
438
486
|
print("=" * 60)
|
439
487
|
else:
|
@@ -444,10 +492,13 @@ class SecurityTestRunner:
|
|
444
492
|
print("\n2. Generate certificates:")
|
445
493
|
print(" python -m mcp_proxy_adapter.examples.generate_certificates")
|
446
494
|
print("\n3. Generate configurations:")
|
447
|
-
print(
|
495
|
+
print(
|
496
|
+
" python -m mcp_proxy_adapter.examples.generate_test_configs --output-dir configs"
|
497
|
+
)
|
448
498
|
print("\n4. Start proxy server manually if needed:")
|
449
499
|
print(" python /path/to/run_proxy_server.py --host 127.0.0.1 --port 3004")
|
450
500
|
print("=" * 60)
|
501
|
+
|
451
502
|
def cleanup(self):
|
452
503
|
"""Cleanup all running servers and proxy."""
|
453
504
|
print("\nš§¹ Cleaning up...")
|
@@ -457,11 +508,13 @@ class SecurityTestRunner:
|
|
457
508
|
self.servers.clear()
|
458
509
|
# Stop proxy server
|
459
510
|
self.stop_proxy_server()
|
511
|
+
|
460
512
|
def signal_handler(self, signum, frame):
|
461
513
|
"""Handle interrupt signals."""
|
462
514
|
print(f"\nā ļø Received signal {signum}, cleaning up...")
|
463
515
|
self.cleanup()
|
464
516
|
sys.exit(0)
|
517
|
+
|
465
518
|
async def run(self):
|
466
519
|
"""Main run method."""
|
467
520
|
# Set up signal handlers
|
@@ -501,12 +554,19 @@ class SecurityTestRunner:
|
|
501
554
|
finally:
|
502
555
|
# Always cleanup
|
503
556
|
self.cleanup()
|
557
|
+
|
558
|
+
|
504
559
|
def main():
|
505
560
|
"""Main function."""
|
506
561
|
import argparse
|
507
|
-
|
562
|
+
|
563
|
+
parser = argparse.ArgumentParser(
|
564
|
+
description="Security Test Runner for MCP Proxy Adapter"
|
565
|
+
)
|
508
566
|
parser.add_argument("--config", help="Test specific configuration")
|
509
|
-
parser.add_argument(
|
567
|
+
parser.add_argument(
|
568
|
+
"--no-cleanup", action="store_true", help="Don't cleanup servers after tests"
|
569
|
+
)
|
510
570
|
parser.add_argument("--verbose", "-v", action="store_true", help="Verbose output")
|
511
571
|
args = parser.parse_args()
|
512
572
|
|
@@ -540,5 +600,7 @@ def main():
|
|
540
600
|
except Exception as e:
|
541
601
|
print(f"ā Unexpected error: {e}")
|
542
602
|
sys.exit(1)
|
603
|
+
|
604
|
+
|
543
605
|
if __name__ == "__main__":
|
544
606
|
main()
|
@@ -15,26 +15,30 @@ import sys
|
|
15
15
|
import time
|
16
16
|
from pathlib import Path
|
17
17
|
from typing import Dict, List, Optional, Tuple
|
18
|
+
|
18
19
|
# Add project root to path
|
19
20
|
project_root = Path(__file__).parent.parent.parent
|
20
21
|
sys.path.insert(0, str(project_root))
|
21
22
|
from security_test_client import SecurityTestClient, TestResult
|
23
|
+
|
24
|
+
|
22
25
|
class SecurityTestRunner:
|
23
26
|
"""Security test runner with proper port management."""
|
27
|
+
|
24
28
|
def __init__(self):
|
25
29
|
self.project_root = Path(__file__).parent.parent.parent
|
26
|
-
self.configs_dir =
|
30
|
+
self.configs_dir = (
|
31
|
+
self.project_root / "mcp_proxy_adapter" / "examples" / "server_configs"
|
32
|
+
)
|
27
33
|
self.server_processes = {}
|
28
34
|
self.test_results = []
|
35
|
+
|
29
36
|
def kill_process_on_port(self, port: int) -> bool:
|
30
37
|
"""Kill process using specific port."""
|
31
38
|
try:
|
32
39
|
# Find process using the port
|
33
40
|
result = subprocess.run(
|
34
|
-
["lsof", "-ti", f":{port}"],
|
35
|
-
capture_output=True,
|
36
|
-
text=True,
|
37
|
-
timeout=5
|
41
|
+
["lsof", "-ti", f":{port}"], capture_output=True, text=True, timeout=5
|
38
42
|
)
|
39
43
|
if result.returncode == 0 and result.stdout.strip():
|
40
44
|
pid = result.stdout.strip()
|
@@ -52,7 +56,10 @@ class SecurityTestRunner:
|
|
52
56
|
except Exception as e:
|
53
57
|
print(f"ā Error killing process on port {port}: {e}")
|
54
58
|
return False
|
55
|
-
|
59
|
+
|
60
|
+
def start_server(
|
61
|
+
self, config_name: str, config_path: Path
|
62
|
+
) -> Optional[subprocess.Popen]:
|
56
63
|
"""Start server with proper error handling."""
|
57
64
|
try:
|
58
65
|
# Get port from config
|
@@ -63,8 +70,11 @@ class SecurityTestRunner:
|
|
63
70
|
self.kill_process_on_port(port)
|
64
71
|
# Start server
|
65
72
|
cmd = [
|
66
|
-
sys.executable,
|
67
|
-
"
|
73
|
+
sys.executable,
|
74
|
+
"-m",
|
75
|
+
"mcp_proxy_adapter.main",
|
76
|
+
"--config",
|
77
|
+
str(config_path),
|
68
78
|
]
|
69
79
|
# For mTLS, start from examples directory
|
70
80
|
if config_name == "mtls":
|
@@ -73,11 +83,7 @@ class SecurityTestRunner:
|
|
73
83
|
cwd = self.project_root
|
74
84
|
print(f"š Starting {config_name} on port {port}...")
|
75
85
|
process = subprocess.Popen(
|
76
|
-
cmd,
|
77
|
-
cwd=cwd,
|
78
|
-
stdout=subprocess.PIPE,
|
79
|
-
stderr=subprocess.PIPE,
|
80
|
-
text=True
|
86
|
+
cmd, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True
|
81
87
|
)
|
82
88
|
# Wait a bit for server to start
|
83
89
|
time.sleep(3)
|
@@ -94,6 +100,7 @@ class SecurityTestRunner:
|
|
94
100
|
except Exception as e:
|
95
101
|
print(f"ā Error starting {config_name}: {e}")
|
96
102
|
return None
|
103
|
+
|
97
104
|
def stop_server(self, config_name: str, process: subprocess.Popen):
|
98
105
|
"""Stop server gracefully."""
|
99
106
|
try:
|
@@ -107,32 +114,43 @@ class SecurityTestRunner:
|
|
107
114
|
process.wait()
|
108
115
|
except Exception as e:
|
109
116
|
print(f"ā Error stopping {config_name}: {e}")
|
110
|
-
|
117
|
+
|
118
|
+
async def test_server(
|
119
|
+
self, config_name: str, config_path: Path
|
120
|
+
) -> List[TestResult]:
|
111
121
|
"""Test a single server configuration."""
|
112
122
|
results = []
|
113
123
|
# Start server
|
114
124
|
process = self.start_server(config_name, config_path)
|
115
125
|
if not process:
|
116
|
-
return [
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
126
|
+
return [
|
127
|
+
TestResult(
|
128
|
+
test_name=f"{config_name}_startup",
|
129
|
+
server_url=f"http://localhost:{port}",
|
130
|
+
auth_type="none",
|
131
|
+
success=False,
|
132
|
+
error_message="Server failed to start",
|
133
|
+
)
|
134
|
+
]
|
123
135
|
try:
|
124
136
|
# Get config for client setup
|
125
137
|
with open(config_path) as f:
|
126
138
|
config = json.load(f)
|
127
139
|
port = config.get("server", {}).get("port", 8000)
|
128
|
-
auth_enabled =
|
140
|
+
auth_enabled = (
|
141
|
+
config.get("security", {}).get("auth", {}).get("enabled", False)
|
142
|
+
)
|
129
143
|
auth_methods = config.get("security", {}).get("auth", {}).get("methods", [])
|
130
144
|
# Create test client with correct protocol
|
131
|
-
protocol =
|
145
|
+
protocol = (
|
146
|
+
"https" if config.get("ssl", {}).get("enabled", False) else "http"
|
147
|
+
)
|
132
148
|
client = SecurityTestClient(base_url=f"{protocol}://localhost:{port}")
|
133
149
|
client.auth_enabled = auth_enabled
|
134
150
|
client.auth_methods = auth_methods
|
135
|
-
client.api_keys =
|
151
|
+
client.api_keys = (
|
152
|
+
config.get("security", {}).get("auth", {}).get("api_keys", {})
|
153
|
+
)
|
136
154
|
# For mTLS, override SSL context creation and change working directory
|
137
155
|
if config_name == "mtls":
|
138
156
|
client.create_ssl_context = client.create_ssl_context_for_mtls
|
@@ -140,6 +158,7 @@ class SecurityTestRunner:
|
|
140
158
|
client.auth_methods = ["certificate"]
|
141
159
|
# Change to examples directory for mTLS tests
|
142
160
|
import os
|
161
|
+
|
143
162
|
os.chdir(self.project_root / "mcp_proxy_adapter" / "examples")
|
144
163
|
# Run tests
|
145
164
|
async with client:
|
@@ -157,13 +176,19 @@ class SecurityTestRunner:
|
|
157
176
|
result = await client.test_negative_authentication()
|
158
177
|
results.append(result)
|
159
178
|
# Test 5: Role-based access
|
160
|
-
result = await client.test_role_based_access(
|
179
|
+
result = await client.test_role_based_access(
|
180
|
+
client.base_url, "api_key"
|
181
|
+
)
|
161
182
|
results.append(result)
|
162
183
|
# Test 6: Role permissions
|
163
|
-
result = await client.test_role_permissions(
|
184
|
+
result = await client.test_role_permissions(
|
185
|
+
client.base_url, "api_key"
|
186
|
+
)
|
164
187
|
results.append(result)
|
165
188
|
# Test 7: Multiple roles test
|
166
|
-
result = await client.test_multiple_roles(
|
189
|
+
result = await client.test_multiple_roles(
|
190
|
+
client.base_url, "api_key"
|
191
|
+
)
|
167
192
|
results.append(result)
|
168
193
|
else:
|
169
194
|
# Test 3: No authentication required
|
@@ -173,17 +198,20 @@ class SecurityTestRunner:
|
|
173
198
|
result = await client.test_negative_authentication()
|
174
199
|
results.append(result)
|
175
200
|
except Exception as e:
|
176
|
-
results.append(
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
201
|
+
results.append(
|
202
|
+
TestResult(
|
203
|
+
test_name=f"{config_name}_client_error",
|
204
|
+
server_url=f"http://localhost:{port}",
|
205
|
+
auth_type="none",
|
206
|
+
success=False,
|
207
|
+
error_message=str(e),
|
208
|
+
)
|
209
|
+
)
|
183
210
|
finally:
|
184
211
|
# Stop server
|
185
212
|
self.stop_server(config_name, process)
|
186
213
|
return results
|
214
|
+
|
187
215
|
async def run_all_tests(self):
|
188
216
|
"""Run all security tests."""
|
189
217
|
print("š Starting Security Testing Suite")
|
@@ -194,7 +222,7 @@ class SecurityTestRunner:
|
|
194
222
|
("http_token", "config_http_token.json"),
|
195
223
|
("https", "config_https.json"),
|
196
224
|
("https_token", "config_https_token.json"),
|
197
|
-
("mtls", "config_mtls.json")
|
225
|
+
("mtls", "config_mtls.json"),
|
198
226
|
]
|
199
227
|
total_tests = 0
|
200
228
|
passed_tests = 0
|
@@ -221,7 +249,11 @@ class SecurityTestRunner:
|
|
221
249
|
print(f"Total tests: {total_tests}")
|
222
250
|
print(f"Passed: {passed_tests}")
|
223
251
|
print(f"Failed: {total_tests - passed_tests}")
|
224
|
-
print(
|
252
|
+
print(
|
253
|
+
f"Success rate: {(passed_tests/total_tests*100):.1f}%"
|
254
|
+
if total_tests > 0
|
255
|
+
else "N/A"
|
256
|
+
)
|
225
257
|
# Detailed results
|
226
258
|
print("\nš DETAILED RESULTS")
|
227
259
|
print("-" * 30)
|
@@ -231,6 +263,8 @@ class SecurityTestRunner:
|
|
231
263
|
if not result.success and result.error_message:
|
232
264
|
print(f" Error: {result.error_message}")
|
233
265
|
return passed_tests == total_tests
|
266
|
+
|
267
|
+
|
234
268
|
async def main():
|
235
269
|
"""Main function."""
|
236
270
|
runner = SecurityTestRunner()
|
@@ -243,5 +277,7 @@ async def main():
|
|
243
277
|
except Exception as e:
|
244
278
|
print(f"\nā Testing failed: {e}")
|
245
279
|
sys.exit(1)
|
280
|
+
|
281
|
+
|
246
282
|
if __name__ == "__main__":
|
247
283
|
asyncio.run(main())
|