mcp-proxy-adapter 4.1.1__py3-none-any.whl → 6.1.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.
Files changed (253) hide show
  1. mcp_proxy_adapter/__main__.py +12 -0
  2. mcp_proxy_adapter/api/app.py +254 -33
  3. mcp_proxy_adapter/api/handlers.py +32 -6
  4. mcp_proxy_adapter/api/middleware/__init__.py +36 -30
  5. mcp_proxy_adapter/api/middleware/command_permission_middleware.py +148 -0
  6. mcp_proxy_adapter/api/middleware/error_handling.py +9 -0
  7. mcp_proxy_adapter/api/middleware/factory.py +243 -0
  8. mcp_proxy_adapter/api/middleware/logging.py +32 -6
  9. mcp_proxy_adapter/api/middleware/protocol_middleware.py +135 -0
  10. mcp_proxy_adapter/api/middleware/transport_middleware.py +122 -0
  11. mcp_proxy_adapter/api/middleware/unified_security.py +152 -0
  12. mcp_proxy_adapter/api/middleware/user_info_middleware.py +83 -0
  13. mcp_proxy_adapter/commands/__init__.py +19 -4
  14. mcp_proxy_adapter/commands/auth_validation_command.py +408 -0
  15. mcp_proxy_adapter/commands/base.py +66 -32
  16. mcp_proxy_adapter/commands/builtin_commands.py +95 -0
  17. mcp_proxy_adapter/commands/catalog_manager.py +838 -0
  18. mcp_proxy_adapter/commands/cert_monitor_command.py +620 -0
  19. mcp_proxy_adapter/commands/certificate_management_command.py +608 -0
  20. mcp_proxy_adapter/commands/command_registry.py +711 -354
  21. mcp_proxy_adapter/commands/dependency_manager.py +245 -0
  22. mcp_proxy_adapter/commands/echo_command.py +81 -0
  23. mcp_proxy_adapter/commands/health_command.py +7 -0
  24. mcp_proxy_adapter/commands/help_command.py +21 -14
  25. mcp_proxy_adapter/commands/hooks.py +200 -167
  26. mcp_proxy_adapter/commands/key_management_command.py +506 -0
  27. mcp_proxy_adapter/commands/load_command.py +176 -0
  28. mcp_proxy_adapter/commands/plugins_command.py +235 -0
  29. mcp_proxy_adapter/commands/protocol_management_command.py +232 -0
  30. mcp_proxy_adapter/commands/proxy_registration_command.py +409 -0
  31. mcp_proxy_adapter/commands/reload_command.py +48 -50
  32. mcp_proxy_adapter/commands/result.py +1 -0
  33. mcp_proxy_adapter/commands/role_test_command.py +141 -0
  34. mcp_proxy_adapter/commands/roles_management_command.py +697 -0
  35. mcp_proxy_adapter/commands/security_command.py +488 -0
  36. mcp_proxy_adapter/commands/ssl_setup_command.py +483 -0
  37. mcp_proxy_adapter/commands/token_management_command.py +529 -0
  38. mcp_proxy_adapter/commands/transport_management_command.py +144 -0
  39. mcp_proxy_adapter/commands/unload_command.py +158 -0
  40. mcp_proxy_adapter/config.py +159 -2
  41. mcp_proxy_adapter/core/app_factory.py +326 -0
  42. mcp_proxy_adapter/core/auth_validator.py +606 -0
  43. mcp_proxy_adapter/core/certificate_utils.py +827 -0
  44. mcp_proxy_adapter/core/client_security.py +384 -0
  45. mcp_proxy_adapter/core/config_converter.py +405 -0
  46. mcp_proxy_adapter/core/config_validator.py +218 -0
  47. mcp_proxy_adapter/core/logging.py +19 -3
  48. mcp_proxy_adapter/core/mtls_asgi.py +156 -0
  49. mcp_proxy_adapter/core/mtls_asgi_app.py +187 -0
  50. mcp_proxy_adapter/core/protocol_manager.py +235 -0
  51. mcp_proxy_adapter/core/proxy_client.py +602 -0
  52. mcp_proxy_adapter/core/proxy_registration.py +522 -0
  53. mcp_proxy_adapter/core/role_utils.py +426 -0
  54. mcp_proxy_adapter/core/security_adapter.py +370 -0
  55. mcp_proxy_adapter/core/security_factory.py +239 -0
  56. mcp_proxy_adapter/core/security_integration.py +277 -0
  57. mcp_proxy_adapter/core/server_adapter.py +345 -0
  58. mcp_proxy_adapter/core/server_engine.py +364 -0
  59. mcp_proxy_adapter/core/settings.py +1 -0
  60. mcp_proxy_adapter/core/ssl_utils.py +233 -0
  61. mcp_proxy_adapter/core/transport_manager.py +292 -0
  62. mcp_proxy_adapter/core/unified_config_adapter.py +579 -0
  63. mcp_proxy_adapter/custom_openapi.py +22 -11
  64. mcp_proxy_adapter/examples/README.md +230 -97
  65. mcp_proxy_adapter/examples/README_EN.md +258 -0
  66. mcp_proxy_adapter/examples/SECURITY_TESTING.md +455 -0
  67. mcp_proxy_adapter/examples/__pycache__/security_configurations.cpython-312.pyc +0 -0
  68. mcp_proxy_adapter/examples/__pycache__/security_test_client.cpython-312.pyc +0 -0
  69. mcp_proxy_adapter/examples/basic_framework/configs/http_auth.json +37 -0
  70. mcp_proxy_adapter/examples/basic_framework/configs/http_simple.json +23 -0
  71. mcp_proxy_adapter/examples/basic_framework/configs/https_auth.json +39 -0
  72. mcp_proxy_adapter/examples/basic_framework/configs/https_simple.json +25 -0
  73. mcp_proxy_adapter/examples/basic_framework/configs/mtls_no_roles.json +39 -0
  74. mcp_proxy_adapter/examples/basic_framework/configs/mtls_with_roles.json +45 -0
  75. mcp_proxy_adapter/examples/basic_framework/main.py +63 -0
  76. mcp_proxy_adapter/examples/basic_framework/roles.json +21 -0
  77. mcp_proxy_adapter/examples/cert_config.json +9 -0
  78. mcp_proxy_adapter/examples/certs/admin.crt +32 -0
  79. mcp_proxy_adapter/examples/certs/admin.key +52 -0
  80. mcp_proxy_adapter/examples/certs/admin_cert.pem +21 -0
  81. mcp_proxy_adapter/examples/certs/admin_key.pem +28 -0
  82. mcp_proxy_adapter/examples/certs/ca_cert.pem +23 -0
  83. mcp_proxy_adapter/examples/certs/ca_cert.srl +1 -0
  84. mcp_proxy_adapter/examples/certs/ca_key.pem +28 -0
  85. mcp_proxy_adapter/examples/certs/cert_config.json +9 -0
  86. mcp_proxy_adapter/examples/certs/client.crt +32 -0
  87. mcp_proxy_adapter/examples/certs/client.key +52 -0
  88. mcp_proxy_adapter/examples/certs/client_admin.crt +32 -0
  89. mcp_proxy_adapter/examples/certs/client_admin.key +52 -0
  90. mcp_proxy_adapter/examples/certs/client_user.crt +32 -0
  91. mcp_proxy_adapter/examples/certs/client_user.key +52 -0
  92. mcp_proxy_adapter/examples/certs/guest_cert.pem +21 -0
  93. mcp_proxy_adapter/examples/certs/guest_key.pem +28 -0
  94. mcp_proxy_adapter/examples/certs/mcp_proxy_adapter_ca_ca.crt +23 -0
  95. mcp_proxy_adapter/examples/certs/proxy_cert.pem +21 -0
  96. mcp_proxy_adapter/examples/certs/proxy_key.pem +28 -0
  97. mcp_proxy_adapter/examples/certs/readonly.crt +32 -0
  98. mcp_proxy_adapter/examples/certs/readonly.key +52 -0
  99. mcp_proxy_adapter/examples/certs/readonly_cert.pem +21 -0
  100. mcp_proxy_adapter/examples/certs/readonly_key.pem +28 -0
  101. mcp_proxy_adapter/examples/certs/server.crt +32 -0
  102. mcp_proxy_adapter/examples/certs/server.key +52 -0
  103. mcp_proxy_adapter/examples/certs/server_cert.pem +32 -0
  104. mcp_proxy_adapter/examples/certs/server_key.pem +52 -0
  105. mcp_proxy_adapter/examples/certs/test_ca_ca.crt +20 -0
  106. mcp_proxy_adapter/examples/certs/user.crt +32 -0
  107. mcp_proxy_adapter/examples/certs/user.key +52 -0
  108. mcp_proxy_adapter/examples/certs/user_cert.pem +21 -0
  109. mcp_proxy_adapter/examples/certs/user_key.pem +28 -0
  110. mcp_proxy_adapter/examples/client_configs/api_key_client.json +13 -0
  111. mcp_proxy_adapter/examples/client_configs/basic_auth_client.json +13 -0
  112. mcp_proxy_adapter/examples/client_configs/certificate_client.json +22 -0
  113. mcp_proxy_adapter/examples/client_configs/jwt_client.json +15 -0
  114. mcp_proxy_adapter/examples/client_configs/no_auth_client.json +9 -0
  115. mcp_proxy_adapter/examples/commands/__init__.py +1 -0
  116. mcp_proxy_adapter/examples/create_certificates_simple.py +307 -0
  117. mcp_proxy_adapter/examples/debug_request_state.py +144 -0
  118. mcp_proxy_adapter/examples/debug_role_chain.py +205 -0
  119. mcp_proxy_adapter/examples/demo_client.py +341 -0
  120. mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +99 -0
  121. mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +106 -0
  122. mcp_proxy_adapter/examples/full_application/configs/http_auth.json +37 -0
  123. mcp_proxy_adapter/examples/full_application/configs/http_simple.json +23 -0
  124. mcp_proxy_adapter/examples/full_application/configs/https_auth.json +39 -0
  125. mcp_proxy_adapter/examples/full_application/configs/https_simple.json +25 -0
  126. mcp_proxy_adapter/examples/full_application/configs/mtls_no_roles.json +39 -0
  127. mcp_proxy_adapter/examples/full_application/configs/mtls_with_roles.json +45 -0
  128. mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +97 -0
  129. mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +95 -0
  130. mcp_proxy_adapter/examples/full_application/main.py +138 -0
  131. mcp_proxy_adapter/examples/full_application/roles.json +21 -0
  132. mcp_proxy_adapter/examples/generate_all_certificates.py +429 -0
  133. mcp_proxy_adapter/examples/generate_certificates.py +121 -0
  134. mcp_proxy_adapter/examples/keys/ca_key.pem +28 -0
  135. mcp_proxy_adapter/examples/keys/mcp_proxy_adapter_ca_ca.key +28 -0
  136. mcp_proxy_adapter/examples/keys/test_ca_ca.key +28 -0
  137. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log +220 -0
  138. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.1 +1 -0
  139. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.2 +1 -0
  140. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.3 +1 -0
  141. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.4 +1 -0
  142. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.5 +1 -0
  143. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log +220 -0
  144. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.1 +1 -0
  145. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.2 +1 -0
  146. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.3 +1 -0
  147. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.4 +1 -0
  148. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.5 +1 -0
  149. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log +2 -0
  150. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.1 +1 -0
  151. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.2 +1 -0
  152. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.3 +1 -0
  153. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.4 +1 -0
  154. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.5 +1 -0
  155. mcp_proxy_adapter/examples/proxy_registration_example.py +401 -0
  156. mcp_proxy_adapter/examples/roles.json +38 -0
  157. mcp_proxy_adapter/examples/run_example.py +81 -0
  158. mcp_proxy_adapter/examples/run_security_tests.py +326 -0
  159. mcp_proxy_adapter/examples/run_security_tests_fixed.py +300 -0
  160. mcp_proxy_adapter/examples/security_test_client.py +743 -0
  161. mcp_proxy_adapter/examples/server_configs/config_basic_http.json +204 -0
  162. mcp_proxy_adapter/examples/server_configs/config_http_token.json +238 -0
  163. mcp_proxy_adapter/examples/server_configs/config_https.json +215 -0
  164. mcp_proxy_adapter/examples/server_configs/config_https_token.json +231 -0
  165. mcp_proxy_adapter/examples/server_configs/config_mtls.json +215 -0
  166. mcp_proxy_adapter/examples/server_configs/config_proxy_registration.json +250 -0
  167. mcp_proxy_adapter/examples/server_configs/config_simple.json +46 -0
  168. mcp_proxy_adapter/examples/server_configs/roles.json +38 -0
  169. mcp_proxy_adapter/examples/test_examples.py +344 -0
  170. mcp_proxy_adapter/examples/universal_client.py +628 -0
  171. mcp_proxy_adapter/main.py +186 -0
  172. mcp_proxy_adapter/utils/config_generator.py +639 -0
  173. mcp_proxy_adapter/version.py +2 -1
  174. mcp_proxy_adapter-6.1.0.dist-info/METADATA +205 -0
  175. mcp_proxy_adapter-6.1.0.dist-info/RECORD +193 -0
  176. mcp_proxy_adapter-6.1.0.dist-info/entry_points.txt +2 -0
  177. {mcp_proxy_adapter-4.1.1.dist-info → mcp_proxy_adapter-6.1.0.dist-info}/licenses/LICENSE +2 -2
  178. mcp_proxy_adapter/api/middleware/auth.py +0 -146
  179. mcp_proxy_adapter/api/middleware/rate_limit.py +0 -152
  180. mcp_proxy_adapter/commands/reload_settings_command.py +0 -125
  181. mcp_proxy_adapter/examples/__init__.py +0 -7
  182. mcp_proxy_adapter/examples/basic_server/README.md +0 -60
  183. mcp_proxy_adapter/examples/basic_server/__init__.py +0 -7
  184. mcp_proxy_adapter/examples/basic_server/basic_custom_settings.json +0 -39
  185. mcp_proxy_adapter/examples/basic_server/config.json +0 -35
  186. mcp_proxy_adapter/examples/basic_server/custom_settings_example.py +0 -238
  187. mcp_proxy_adapter/examples/basic_server/server.py +0 -103
  188. mcp_proxy_adapter/examples/custom_commands/README.md +0 -127
  189. mcp_proxy_adapter/examples/custom_commands/__init__.py +0 -27
  190. mcp_proxy_adapter/examples/custom_commands/advanced_hooks.py +0 -250
  191. mcp_proxy_adapter/examples/custom_commands/auto_commands/__init__.py +0 -6
  192. mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_echo_command.py +0 -103
  193. mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_info_command.py +0 -111
  194. mcp_proxy_adapter/examples/custom_commands/config.json +0 -35
  195. mcp_proxy_adapter/examples/custom_commands/custom_health_command.py +0 -169
  196. mcp_proxy_adapter/examples/custom_commands/custom_help_command.py +0 -215
  197. mcp_proxy_adapter/examples/custom_commands/custom_openapi_generator.py +0 -76
  198. mcp_proxy_adapter/examples/custom_commands/custom_settings.json +0 -96
  199. mcp_proxy_adapter/examples/custom_commands/custom_settings_manager.py +0 -241
  200. mcp_proxy_adapter/examples/custom_commands/data_transform_command.py +0 -135
  201. mcp_proxy_adapter/examples/custom_commands/echo_command.py +0 -122
  202. mcp_proxy_adapter/examples/custom_commands/hooks.py +0 -230
  203. mcp_proxy_adapter/examples/custom_commands/intercept_command.py +0 -123
  204. mcp_proxy_adapter/examples/custom_commands/manual_echo_command.py +0 -103
  205. mcp_proxy_adapter/examples/custom_commands/server.py +0 -228
  206. mcp_proxy_adapter/examples/custom_commands/test_hooks.py +0 -176
  207. mcp_proxy_adapter/examples/deployment/README.md +0 -49
  208. mcp_proxy_adapter/examples/deployment/__init__.py +0 -7
  209. mcp_proxy_adapter/examples/deployment/config.development.json +0 -8
  210. mcp_proxy_adapter/examples/deployment/config.json +0 -29
  211. mcp_proxy_adapter/examples/deployment/config.production.json +0 -12
  212. mcp_proxy_adapter/examples/deployment/config.staging.json +0 -11
  213. mcp_proxy_adapter/examples/deployment/docker-compose.yml +0 -31
  214. mcp_proxy_adapter/examples/deployment/run.sh +0 -43
  215. mcp_proxy_adapter/examples/deployment/run_docker.sh +0 -84
  216. mcp_proxy_adapter/schemas/base_schema.json +0 -114
  217. mcp_proxy_adapter/schemas/openapi_schema.json +0 -314
  218. mcp_proxy_adapter/tests/__init__.py +0 -0
  219. mcp_proxy_adapter/tests/api/__init__.py +0 -3
  220. mcp_proxy_adapter/tests/api/test_cmd_endpoint.py +0 -115
  221. mcp_proxy_adapter/tests/api/test_custom_openapi.py +0 -617
  222. mcp_proxy_adapter/tests/api/test_handlers.py +0 -522
  223. mcp_proxy_adapter/tests/api/test_middleware.py +0 -340
  224. mcp_proxy_adapter/tests/api/test_schemas.py +0 -546
  225. mcp_proxy_adapter/tests/api/test_tool_integration.py +0 -531
  226. mcp_proxy_adapter/tests/commands/__init__.py +0 -3
  227. mcp_proxy_adapter/tests/commands/test_config_command.py +0 -211
  228. mcp_proxy_adapter/tests/commands/test_echo_command.py +0 -127
  229. mcp_proxy_adapter/tests/commands/test_help_command.py +0 -136
  230. mcp_proxy_adapter/tests/conftest.py +0 -131
  231. mcp_proxy_adapter/tests/functional/__init__.py +0 -3
  232. mcp_proxy_adapter/tests/functional/test_api.py +0 -253
  233. mcp_proxy_adapter/tests/integration/__init__.py +0 -3
  234. mcp_proxy_adapter/tests/integration/test_cmd_integration.py +0 -129
  235. mcp_proxy_adapter/tests/integration/test_integration.py +0 -255
  236. mcp_proxy_adapter/tests/performance/__init__.py +0 -3
  237. mcp_proxy_adapter/tests/performance/test_performance.py +0 -189
  238. mcp_proxy_adapter/tests/stubs/__init__.py +0 -10
  239. mcp_proxy_adapter/tests/stubs/echo_command.py +0 -104
  240. mcp_proxy_adapter/tests/test_api_endpoints.py +0 -271
  241. mcp_proxy_adapter/tests/test_api_handlers.py +0 -289
  242. mcp_proxy_adapter/tests/test_base_command.py +0 -123
  243. mcp_proxy_adapter/tests/test_batch_requests.py +0 -117
  244. mcp_proxy_adapter/tests/test_command_registry.py +0 -281
  245. mcp_proxy_adapter/tests/test_config.py +0 -127
  246. mcp_proxy_adapter/tests/test_utils.py +0 -65
  247. mcp_proxy_adapter/tests/unit/__init__.py +0 -3
  248. mcp_proxy_adapter/tests/unit/test_base_command.py +0 -436
  249. mcp_proxy_adapter/tests/unit/test_config.py +0 -217
  250. mcp_proxy_adapter-4.1.1.dist-info/METADATA +0 -200
  251. mcp_proxy_adapter-4.1.1.dist-info/RECORD +0 -110
  252. {mcp_proxy_adapter-4.1.1.dist-info → mcp_proxy_adapter-6.1.0.dist-info}/WHEEL +0 -0
  253. {mcp_proxy_adapter-4.1.1.dist-info → mcp_proxy_adapter-6.1.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,326 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Security Test Runner for MCP Proxy Adapter
4
+
5
+ This script runs comprehensive security tests against all server configurations:
6
+ - Basic HTTP
7
+ - HTTP + Token authentication
8
+ - HTTPS
9
+ - HTTPS + Token authentication
10
+ - mTLS
11
+
12
+ Author: Vasiliy Zdanovskiy
13
+ email: vasilyvz@gmail.com
14
+ """
15
+
16
+ import asyncio
17
+ import json
18
+ import os
19
+ import signal
20
+ import subprocess
21
+ import sys
22
+ import time
23
+ from pathlib import Path
24
+ from typing import Dict, List, Optional, Any
25
+
26
+ # Add project root to path
27
+ project_root = Path(__file__).parent.parent.parent
28
+ sys.path.insert(0, str(project_root))
29
+
30
+ from security_test_client import SecurityTestClient, TestResult
31
+
32
+
33
+ class SecurityTestRunner:
34
+ """Main test runner for security testing."""
35
+
36
+ def __init__(self):
37
+ """Initialize test runner."""
38
+ self.servers = {}
39
+ self.test_results = {}
40
+ self.configs = {
41
+ "basic_http": {
42
+ "config": "server_configs/config_basic_http.json",
43
+ "port": 8000,
44
+ "url": "http://localhost:8000",
45
+ "auth": "none"
46
+ },
47
+ "http_token": {
48
+ "config": "server_configs/config_http_token.json",
49
+ "port": 8001,
50
+ "url": "http://localhost:8001",
51
+ "auth": "api_key"
52
+ },
53
+ "https": {
54
+ "config": "server_configs/config_https.json",
55
+ "port": 8443,
56
+ "url": "https://localhost:8443",
57
+ "auth": "none"
58
+ },
59
+ "https_token": {
60
+ "config": "server_configs/config_https_token.json",
61
+ "port": 8444,
62
+ "url": "https://localhost:8444",
63
+ "auth": "api_key"
64
+ },
65
+ "mtls": {
66
+ "config": "server_configs/config_mtls.json",
67
+ "port": 8445,
68
+ "url": "https://localhost:8445",
69
+ "auth": "certificate"
70
+ }
71
+ }
72
+
73
+ def check_prerequisites(self) -> bool:
74
+ """Check if all prerequisites are met."""
75
+ print("šŸ” Checking prerequisites...")
76
+
77
+ # Check if we're in the right directory
78
+ if not Path("server_configs").exists():
79
+ print("āŒ server_configs directory not found. Please run from mcp_proxy_adapter/examples/")
80
+ return False
81
+
82
+ # Check if certificates exist
83
+ cert_files = [
84
+ "certs/ca_cert.pem",
85
+ "certs/server_cert.pem",
86
+ "certs/server_key.pem"
87
+ ]
88
+
89
+ missing_certs = []
90
+ for cert_file in cert_files:
91
+ if not Path(cert_file).exists():
92
+ missing_certs.append(cert_file)
93
+
94
+ if missing_certs:
95
+ print(f"āŒ Missing certificates: {missing_certs}")
96
+ print("šŸ’” Run: python generate_certificates.py")
97
+ return False
98
+
99
+ print("āœ… Prerequisites check passed")
100
+ return True
101
+
102
+ def start_server(self, name: str, config_path: str, port: int) -> Optional[subprocess.Popen]:
103
+ """Start a server in background."""
104
+ try:
105
+ print(f"šŸš€ Starting {name} server on port {port}...")
106
+
107
+ # Start server in background
108
+ process = subprocess.Popen([
109
+ sys.executable, "-m", "mcp_proxy_adapter.main",
110
+ "--config", config_path
111
+ ], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
112
+
113
+ # Wait a bit for server to start
114
+ time.sleep(3)
115
+
116
+ # Check if process is still running
117
+ if process.poll() is None:
118
+ print(f"āœ… {name} server started (PID: {process.pid})")
119
+ return process
120
+ else:
121
+ stdout, stderr = process.communicate()
122
+ print(f"āŒ Failed to start {name} server:")
123
+ print(f"STDOUT: {stdout.decode()}")
124
+ print(f"STDERR: {stderr.decode()}")
125
+ return None
126
+
127
+ except Exception as e:
128
+ print(f"āŒ Error starting {name} server: {e}")
129
+ return None
130
+
131
+ def stop_server(self, name: str, process: subprocess.Popen):
132
+ """Stop a server."""
133
+ try:
134
+ print(f"šŸ›‘ Stopping {name} server (PID: {process.pid})...")
135
+ process.terminate()
136
+
137
+ # Wait for graceful shutdown
138
+ try:
139
+ process.wait(timeout=5)
140
+ print(f"āœ… {name} server stopped")
141
+ except subprocess.TimeoutExpired:
142
+ print(f"āš ļø Force killing {name} server")
143
+ process.kill()
144
+ process.wait()
145
+
146
+ except Exception as e:
147
+ print(f"āŒ Error stopping {name} server: {e}")
148
+
149
+ async def test_server(self, name: str, config: Dict[str, Any]) -> List[TestResult]:
150
+ """Test a specific server configuration."""
151
+ print(f"\n🧪 Testing {name} server...")
152
+ print("=" * 50)
153
+
154
+ # Create client with appropriate SSL context
155
+ if config["auth"] == "certificate":
156
+ # For mTLS, create client with certificate-based SSL context
157
+ client = SecurityTestClient(config["url"])
158
+ # Override SSL context for mTLS
159
+ client.create_ssl_context = client.create_ssl_context_for_mtls
160
+ async with client as client_session:
161
+ results = await client_session.run_security_tests(
162
+ config["url"],
163
+ config["auth"]
164
+ )
165
+ else:
166
+ # For other auth types, use default SSL context
167
+ async with SecurityTestClient(config["url"]) as client:
168
+ results = await client.run_security_tests(
169
+ config["url"],
170
+ config["auth"]
171
+ )
172
+
173
+ # Print summary for this server
174
+ passed = sum(1 for r in results if r.success)
175
+ total = len(results)
176
+ print(f"\nšŸ“Š {name} Results: {passed}/{total} tests passed")
177
+
178
+ return results
179
+
180
+ async def run_all_tests(self) -> Dict[str, List[TestResult]]:
181
+ """Run tests against all server configurations."""
182
+ print("šŸš€ Starting comprehensive security testing")
183
+ print("=" * 60)
184
+
185
+ # Start all servers
186
+ for name, config in self.configs.items():
187
+ process = self.start_server(name, config["config"], config["port"])
188
+ if process:
189
+ self.servers[name] = process
190
+ else:
191
+ print(f"āš ļø Skipping tests for {name} due to startup failure")
192
+
193
+ # Wait for all servers to be ready
194
+ print("\nā³ Waiting for servers to be ready...")
195
+ time.sleep(5)
196
+
197
+ # Test each server
198
+ all_results = {}
199
+ for name, config in self.configs.items():
200
+ if name in self.servers:
201
+ try:
202
+ results = await self.test_server(name, config)
203
+ all_results[name] = results
204
+ except Exception as e:
205
+ print(f"āŒ Error testing {name}: {e}")
206
+ all_results[name] = []
207
+ else:
208
+ print(f"āš ļø Skipping {name} tests (server not running)")
209
+ all_results[name] = []
210
+
211
+ return all_results
212
+
213
+ def print_final_summary(self, all_results: Dict[str, List[TestResult]]):
214
+ """Print final test summary."""
215
+ print("\n" + "=" * 80)
216
+ print("šŸ“Š FINAL SECURITY TEST SUMMARY")
217
+ print("=" * 80)
218
+
219
+ total_tests = 0
220
+ total_passed = 0
221
+
222
+ for server_name, results in all_results.items():
223
+ if results:
224
+ passed = sum(1 for r in results if r.success)
225
+ total = len(results)
226
+ total_tests += total
227
+ total_passed += passed
228
+
229
+ status = "āœ… PASS" if passed == total else "āŒ FAIL"
230
+ print(f"{status} {server_name.upper()}: {passed}/{total} tests passed")
231
+
232
+ # Show failed tests
233
+ failed_tests = [r for r in results if not r.success]
234
+ for test in failed_tests:
235
+ print(f" āŒ {test.test_name}: {test.error_message}")
236
+ else:
237
+ print(f"āš ļø SKIP {server_name.upper()}: No tests run")
238
+
239
+ print("\n" + "-" * 80)
240
+ print(f"OVERALL: {total_passed}/{total_tests} tests passed")
241
+ if total_tests > 0:
242
+ success_rate = (total_passed / total_tests) * 100
243
+ print(f"SUCCESS RATE: {success_rate:.1f}%")
244
+
245
+ # Overall status
246
+ if total_passed == total_tests and total_tests > 0:
247
+ print("šŸŽ‰ ALL TESTS PASSED!")
248
+ elif total_passed > 0:
249
+ print("āš ļø SOME TESTS FAILED")
250
+ else:
251
+ print("āŒ ALL TESTS FAILED")
252
+
253
+ def cleanup(self):
254
+ """Cleanup all running servers."""
255
+ print("\n🧹 Cleaning up...")
256
+
257
+ for name, process in self.servers.items():
258
+ self.stop_server(name, process)
259
+
260
+ self.servers.clear()
261
+
262
+ def signal_handler(self, signum, frame):
263
+ """Handle interrupt signals."""
264
+ print(f"\nāš ļø Received signal {signum}, cleaning up...")
265
+ self.cleanup()
266
+ sys.exit(0)
267
+
268
+ async def run(self):
269
+ """Main run method."""
270
+ # Set up signal handlers
271
+ signal.signal(signal.SIGINT, self.signal_handler)
272
+ signal.signal(signal.SIGTERM, self.signal_handler)
273
+
274
+ try:
275
+ # Check prerequisites
276
+ if not self.check_prerequisites():
277
+ return False
278
+
279
+ # Run all tests
280
+ all_results = await self.run_all_tests()
281
+
282
+ # Print summary
283
+ self.print_final_summary(all_results)
284
+
285
+ return True
286
+
287
+ except Exception as e:
288
+ print(f"āŒ Test runner error: {e}")
289
+ return False
290
+
291
+ finally:
292
+ # Always cleanup
293
+ self.cleanup()
294
+
295
+
296
+ def main():
297
+ """Main function."""
298
+ import argparse
299
+
300
+ parser = argparse.ArgumentParser(description="Security Test Runner for MCP Proxy Adapter")
301
+ parser.add_argument("--config", help="Test specific configuration")
302
+ parser.add_argument("--no-cleanup", action="store_true", help="Don't cleanup servers after tests")
303
+ parser.add_argument("--verbose", "-v", action="store_true", help="Verbose output")
304
+
305
+ args = parser.parse_args()
306
+
307
+ # Change to examples directory
308
+ examples_dir = Path(__file__).parent
309
+ os.chdir(examples_dir)
310
+
311
+ # Create and run test runner
312
+ runner = SecurityTestRunner()
313
+
314
+ try:
315
+ success = asyncio.run(runner.run())
316
+ sys.exit(0 if success else 1)
317
+ except KeyboardInterrupt:
318
+ print("\nāš ļø Interrupted by user")
319
+ sys.exit(1)
320
+ except Exception as e:
321
+ print(f"āŒ Unexpected error: {e}")
322
+ sys.exit(1)
323
+
324
+
325
+ if __name__ == "__main__":
326
+ main()
@@ -0,0 +1,300 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Security Testing Script - Fixed Version
4
+
5
+ This script runs comprehensive security tests without fallback mode
6
+ and with proper port management.
7
+
8
+ Author: Vasiliy Zdanovskiy
9
+ email: vasilyvz@gmail.com
10
+ """
11
+
12
+ import asyncio
13
+ import json
14
+ import os
15
+ import signal
16
+ import subprocess
17
+ import sys
18
+ import time
19
+ from pathlib import Path
20
+ from typing import Dict, List, Optional, Tuple
21
+
22
+ # Add project root to path
23
+ project_root = Path(__file__).parent.parent.parent
24
+ sys.path.insert(0, str(project_root))
25
+
26
+ from security_test_client import SecurityTestClient, TestResult
27
+
28
+
29
+ class SecurityTestRunner:
30
+ """Security test runner with proper port management."""
31
+
32
+ def __init__(self):
33
+ self.project_root = Path(__file__).parent.parent.parent
34
+ self.configs_dir = self.project_root / "mcp_proxy_adapter" / "examples" / "server_configs"
35
+ self.server_processes = {}
36
+ self.test_results = []
37
+
38
+ def kill_process_on_port(self, port: int) -> bool:
39
+ """Kill process using specific port."""
40
+ try:
41
+ # Find process using the port
42
+ result = subprocess.run(
43
+ ["lsof", "-ti", f":{port}"],
44
+ capture_output=True,
45
+ text=True,
46
+ timeout=5
47
+ )
48
+
49
+ if result.returncode == 0 and result.stdout.strip():
50
+ pid = result.stdout.strip()
51
+ # Kill the process
52
+ subprocess.run(["kill", "-9", pid], check=True)
53
+ print(f"āœ… Killed process {pid} on port {port}")
54
+ time.sleep(1) # Wait for port to be released
55
+ return True
56
+ else:
57
+ print(f"ā„¹ļø No process found on port {port}")
58
+ return True
59
+ except subprocess.TimeoutExpired:
60
+ print(f"āš ļø Timeout checking port {port}")
61
+ return False
62
+ except Exception as e:
63
+ print(f"āŒ Error killing process on port {port}: {e}")
64
+ return False
65
+
66
+ def start_server(self, config_name: str, config_path: Path) -> Optional[subprocess.Popen]:
67
+ """Start server with proper error handling."""
68
+ try:
69
+ # Get port from config
70
+ with open(config_path) as f:
71
+ config = json.load(f)
72
+ port = config.get("server", {}).get("port", 8000)
73
+
74
+ # Kill any existing process on this port
75
+ self.kill_process_on_port(port)
76
+
77
+ # Start server
78
+ cmd = [
79
+ sys.executable, "-m", "mcp_proxy_adapter.main",
80
+ "--config", str(config_path)
81
+ ]
82
+
83
+ # For mTLS, start from examples directory
84
+ if config_name == "mtls":
85
+ cwd = self.project_root / "mcp_proxy_adapter" / "examples"
86
+ else:
87
+ cwd = self.project_root
88
+
89
+ print(f"šŸš€ Starting {config_name} on port {port}...")
90
+ process = subprocess.Popen(
91
+ cmd,
92
+ cwd=cwd,
93
+ stdout=subprocess.PIPE,
94
+ stderr=subprocess.PIPE,
95
+ text=True
96
+ )
97
+
98
+ # Wait a bit for server to start
99
+ time.sleep(3)
100
+
101
+ # Check if process is still running
102
+ if process.poll() is None:
103
+ print(f"āœ… {config_name} started successfully on port {port}")
104
+ return process
105
+ else:
106
+ stdout, stderr = process.communicate()
107
+ print(f"āŒ {config_name} failed to start:")
108
+ print(f"STDOUT: {stdout}")
109
+ print(f"STDERR: {stderr}")
110
+ return None
111
+
112
+ except Exception as e:
113
+ print(f"āŒ Error starting {config_name}: {e}")
114
+ return None
115
+
116
+ def stop_server(self, config_name: str, process: subprocess.Popen):
117
+ """Stop server gracefully."""
118
+ try:
119
+ print(f"šŸ›‘ Stopping {config_name}...")
120
+ process.terminate()
121
+ process.wait(timeout=5)
122
+ print(f"āœ… {config_name} stopped")
123
+ except subprocess.TimeoutExpired:
124
+ print(f"āš ļø Force killing {config_name}...")
125
+ process.kill()
126
+ process.wait()
127
+ except Exception as e:
128
+ print(f"āŒ Error stopping {config_name}: {e}")
129
+
130
+ async def test_server(self, config_name: str, config_path: Path) -> List[TestResult]:
131
+ """Test a single server configuration."""
132
+ results = []
133
+
134
+ # Start server
135
+ process = self.start_server(config_name, config_path)
136
+ if not process:
137
+ return [TestResult(
138
+ test_name=f"{config_name}_startup",
139
+ server_url=f"http://localhost:{port}",
140
+ auth_type="none",
141
+ success=False,
142
+ error_message="Server failed to start"
143
+ )]
144
+
145
+ try:
146
+ # Get config for client setup
147
+ with open(config_path) as f:
148
+ config = json.load(f)
149
+
150
+ port = config.get("server", {}).get("port", 8000)
151
+ auth_enabled = config.get("security", {}).get("auth", {}).get("enabled", False)
152
+ auth_methods = config.get("security", {}).get("auth", {}).get("methods", [])
153
+
154
+ # Create test client with correct protocol
155
+ protocol = "https" if config.get("ssl", {}).get("enabled", False) else "http"
156
+ client = SecurityTestClient(base_url=f"{protocol}://localhost:{port}")
157
+ client.auth_enabled = auth_enabled
158
+ client.auth_methods = auth_methods
159
+ client.api_keys = config.get("security", {}).get("auth", {}).get("api_keys", {})
160
+
161
+ # For mTLS, override SSL context creation and change working directory
162
+ if config_name == "mtls":
163
+ client.create_ssl_context = client.create_ssl_context_for_mtls
164
+ # Ensure mTLS uses certificate auth
165
+ client.auth_methods = ["certificate"]
166
+ # Change to examples directory for mTLS tests
167
+ import os
168
+ os.chdir(self.project_root / "mcp_proxy_adapter" / "examples")
169
+
170
+ # Run tests
171
+ async with client:
172
+ # Test 1: Health check
173
+ result = await client.test_health()
174
+ results.append(result)
175
+
176
+ # Test 2: Command execution
177
+ result = await client.test_command_execution()
178
+ results.append(result)
179
+
180
+ # Test 3: Authentication (if enabled)
181
+ if auth_enabled:
182
+ result = await client.test_authentication()
183
+ results.append(result)
184
+
185
+ # Test 4: Negative authentication
186
+ result = await client.test_negative_authentication()
187
+ results.append(result)
188
+
189
+ # Test 5: Role-based access
190
+ result = await client.test_role_based_access(client.base_url, "api_key")
191
+ results.append(result)
192
+
193
+ # Test 6: Role permissions
194
+ result = await client.test_role_permissions(client.base_url, "api_key")
195
+ results.append(result)
196
+
197
+ # Test 7: Multiple roles test
198
+ result = await client.test_multiple_roles(client.base_url, "api_key")
199
+ results.append(result)
200
+ else:
201
+ # Test 3: No authentication required
202
+ result = await client.test_no_auth_required()
203
+ results.append(result)
204
+
205
+ # Test 4: Negative auth (should fail)
206
+ result = await client.test_negative_authentication()
207
+ results.append(result)
208
+
209
+ except Exception as e:
210
+ results.append(TestResult(
211
+ test_name=f"{config_name}_client_error",
212
+ server_url=f"http://localhost:{port}",
213
+ auth_type="none",
214
+ success=False,
215
+ error_message=str(e)
216
+ ))
217
+
218
+ finally:
219
+ # Stop server
220
+ self.stop_server(config_name, process)
221
+
222
+ return results
223
+
224
+ async def run_all_tests(self):
225
+ """Run all security tests."""
226
+ print("šŸ”’ Starting Security Testing Suite")
227
+ print("=" * 50)
228
+
229
+ # Test configurations
230
+ configs = [
231
+ ("basic_http", "config_basic_http.json"),
232
+ ("http_token", "config_http_token.json"),
233
+ ("https", "config_https.json"),
234
+ ("https_token", "config_https_token.json"),
235
+ ("mtls", "config_mtls.json")
236
+ ]
237
+
238
+ total_tests = 0
239
+ passed_tests = 0
240
+
241
+ for config_name, config_file in configs:
242
+ config_path = self.configs_dir / config_file
243
+
244
+ if not config_path.exists():
245
+ print(f"āŒ Configuration not found: {config_path}")
246
+ continue
247
+
248
+ print(f"\nšŸ“‹ Testing {config_name.upper()} configuration")
249
+ print("-" * 30)
250
+
251
+ results = await self.test_server(config_name, config_path)
252
+
253
+ for result in results:
254
+ total_tests += 1
255
+ if result.success:
256
+ passed_tests += 1
257
+ print(f"āœ… {result.test_name}: PASS")
258
+ else:
259
+ print(f"āŒ {result.test_name}: FAIL - {result.error_message}")
260
+
261
+ self.test_results.extend(results)
262
+
263
+ # Print summary
264
+ print("\n" + "=" * 50)
265
+ print("šŸ“Š TEST SUMMARY")
266
+ print("=" * 50)
267
+ print(f"Total tests: {total_tests}")
268
+ print(f"Passed: {passed_tests}")
269
+ print(f"Failed: {total_tests - passed_tests}")
270
+ print(f"Success rate: {(passed_tests/total_tests*100):.1f}%" if total_tests > 0 else "N/A")
271
+
272
+ # Detailed results
273
+ print("\nšŸ“‹ DETAILED RESULTS")
274
+ print("-" * 30)
275
+ for result in self.test_results:
276
+ status = "āœ… PASS" if result.success else "āŒ FAIL"
277
+ print(f"{status} {result.test_name}")
278
+ if not result.success and result.error_message:
279
+ print(f" Error: {result.error_message}")
280
+
281
+ return passed_tests == total_tests
282
+
283
+
284
+ async def main():
285
+ """Main function."""
286
+ runner = SecurityTestRunner()
287
+
288
+ try:
289
+ success = await runner.run_all_tests()
290
+ sys.exit(0 if success else 1)
291
+ except KeyboardInterrupt:
292
+ print("\nāš ļø Testing interrupted by user")
293
+ sys.exit(1)
294
+ except Exception as e:
295
+ print(f"\nāŒ Testing failed: {e}")
296
+ sys.exit(1)
297
+
298
+
299
+ if __name__ == "__main__":
300
+ asyncio.run(main())