mcp-proxy-adapter 6.0.0__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 (259) hide show
  1. mcp_proxy_adapter/api/app.py +174 -80
  2. mcp_proxy_adapter/api/handlers.py +16 -5
  3. mcp_proxy_adapter/api/middleware/__init__.py +7 -2
  4. mcp_proxy_adapter/api/middleware/command_permission_middleware.py +148 -0
  5. mcp_proxy_adapter/api/middleware/factory.py +36 -12
  6. mcp_proxy_adapter/api/middleware/unified_security.py +152 -0
  7. mcp_proxy_adapter/api/middleware/user_info_middleware.py +83 -0
  8. mcp_proxy_adapter/commands/__init__.py +7 -1
  9. mcp_proxy_adapter/commands/base.py +7 -4
  10. mcp_proxy_adapter/commands/builtin_commands.py +8 -2
  11. mcp_proxy_adapter/commands/command_registry.py +8 -0
  12. mcp_proxy_adapter/commands/echo_command.py +81 -0
  13. mcp_proxy_adapter/commands/help_command.py +21 -14
  14. mcp_proxy_adapter/commands/proxy_registration_command.py +326 -185
  15. mcp_proxy_adapter/commands/role_test_command.py +141 -0
  16. mcp_proxy_adapter/commands/security_command.py +488 -0
  17. mcp_proxy_adapter/commands/ssl_setup_command.py +2 -2
  18. mcp_proxy_adapter/commands/token_management_command.py +1 -1
  19. mcp_proxy_adapter/config.py +81 -21
  20. mcp_proxy_adapter/core/app_factory.py +326 -0
  21. mcp_proxy_adapter/core/client_security.py +384 -0
  22. mcp_proxy_adapter/core/logging.py +8 -3
  23. mcp_proxy_adapter/core/mtls_asgi.py +156 -0
  24. mcp_proxy_adapter/core/mtls_asgi_app.py +187 -0
  25. mcp_proxy_adapter/core/protocol_manager.py +9 -0
  26. mcp_proxy_adapter/core/proxy_client.py +602 -0
  27. mcp_proxy_adapter/core/proxy_registration.py +299 -47
  28. mcp_proxy_adapter/core/security_adapter.py +12 -15
  29. mcp_proxy_adapter/core/security_integration.py +277 -0
  30. mcp_proxy_adapter/core/server_adapter.py +345 -0
  31. mcp_proxy_adapter/core/server_engine.py +364 -0
  32. mcp_proxy_adapter/core/unified_config_adapter.py +579 -0
  33. mcp_proxy_adapter/examples/README.md +230 -97
  34. mcp_proxy_adapter/examples/README_EN.md +258 -0
  35. mcp_proxy_adapter/examples/SECURITY_TESTING.md +455 -0
  36. mcp_proxy_adapter/examples/__pycache__/security_configurations.cpython-312.pyc +0 -0
  37. mcp_proxy_adapter/examples/__pycache__/security_test_client.cpython-312.pyc +0 -0
  38. mcp_proxy_adapter/examples/basic_framework/configs/http_auth.json +37 -0
  39. mcp_proxy_adapter/examples/basic_framework/configs/http_simple.json +23 -0
  40. mcp_proxy_adapter/examples/basic_framework/configs/https_auth.json +39 -0
  41. mcp_proxy_adapter/examples/basic_framework/configs/https_simple.json +25 -0
  42. mcp_proxy_adapter/examples/basic_framework/configs/mtls_no_roles.json +39 -0
  43. mcp_proxy_adapter/examples/basic_framework/configs/mtls_with_roles.json +45 -0
  44. mcp_proxy_adapter/examples/basic_framework/main.py +63 -0
  45. mcp_proxy_adapter/examples/basic_framework/roles.json +21 -0
  46. mcp_proxy_adapter/examples/cert_config.json +9 -0
  47. mcp_proxy_adapter/examples/certs/admin.crt +32 -0
  48. mcp_proxy_adapter/examples/certs/admin.key +52 -0
  49. mcp_proxy_adapter/examples/certs/admin_cert.pem +21 -0
  50. mcp_proxy_adapter/examples/certs/admin_key.pem +28 -0
  51. mcp_proxy_adapter/examples/certs/ca_cert.pem +23 -0
  52. mcp_proxy_adapter/examples/certs/ca_cert.srl +1 -0
  53. mcp_proxy_adapter/examples/certs/ca_key.pem +28 -0
  54. mcp_proxy_adapter/examples/certs/cert_config.json +9 -0
  55. mcp_proxy_adapter/examples/certs/client.crt +32 -0
  56. mcp_proxy_adapter/examples/certs/client.key +52 -0
  57. mcp_proxy_adapter/examples/certs/client_admin.crt +32 -0
  58. mcp_proxy_adapter/examples/certs/client_admin.key +52 -0
  59. mcp_proxy_adapter/examples/certs/client_user.crt +32 -0
  60. mcp_proxy_adapter/examples/certs/client_user.key +52 -0
  61. mcp_proxy_adapter/examples/certs/guest_cert.pem +21 -0
  62. mcp_proxy_adapter/examples/certs/guest_key.pem +28 -0
  63. mcp_proxy_adapter/examples/certs/mcp_proxy_adapter_ca_ca.crt +23 -0
  64. mcp_proxy_adapter/examples/certs/proxy_cert.pem +21 -0
  65. mcp_proxy_adapter/examples/certs/proxy_key.pem +28 -0
  66. mcp_proxy_adapter/examples/certs/readonly.crt +32 -0
  67. mcp_proxy_adapter/examples/certs/readonly.key +52 -0
  68. mcp_proxy_adapter/examples/certs/readonly_cert.pem +21 -0
  69. mcp_proxy_adapter/examples/certs/readonly_key.pem +28 -0
  70. mcp_proxy_adapter/examples/certs/server.crt +32 -0
  71. mcp_proxy_adapter/examples/certs/server.key +52 -0
  72. mcp_proxy_adapter/examples/certs/server_cert.pem +32 -0
  73. mcp_proxy_adapter/examples/certs/server_key.pem +52 -0
  74. mcp_proxy_adapter/examples/certs/test_ca_ca.crt +20 -0
  75. mcp_proxy_adapter/examples/certs/user.crt +32 -0
  76. mcp_proxy_adapter/examples/certs/user.key +52 -0
  77. mcp_proxy_adapter/examples/certs/user_cert.pem +21 -0
  78. mcp_proxy_adapter/examples/certs/user_key.pem +28 -0
  79. mcp_proxy_adapter/examples/client_configs/api_key_client.json +13 -0
  80. mcp_proxy_adapter/examples/client_configs/basic_auth_client.json +13 -0
  81. mcp_proxy_adapter/examples/client_configs/certificate_client.json +22 -0
  82. mcp_proxy_adapter/examples/client_configs/jwt_client.json +15 -0
  83. mcp_proxy_adapter/examples/client_configs/no_auth_client.json +9 -0
  84. mcp_proxy_adapter/examples/commands/__init__.py +1 -0
  85. mcp_proxy_adapter/examples/create_certificates_simple.py +307 -0
  86. mcp_proxy_adapter/examples/debug_request_state.py +144 -0
  87. mcp_proxy_adapter/examples/debug_role_chain.py +205 -0
  88. mcp_proxy_adapter/examples/demo_client.py +341 -0
  89. mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +99 -0
  90. mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +106 -0
  91. mcp_proxy_adapter/examples/full_application/configs/http_auth.json +37 -0
  92. mcp_proxy_adapter/examples/full_application/configs/http_simple.json +23 -0
  93. mcp_proxy_adapter/examples/full_application/configs/https_auth.json +39 -0
  94. mcp_proxy_adapter/examples/full_application/configs/https_simple.json +25 -0
  95. mcp_proxy_adapter/examples/full_application/configs/mtls_no_roles.json +39 -0
  96. mcp_proxy_adapter/examples/full_application/configs/mtls_with_roles.json +45 -0
  97. mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +97 -0
  98. mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +95 -0
  99. mcp_proxy_adapter/examples/full_application/main.py +138 -0
  100. mcp_proxy_adapter/examples/full_application/roles.json +21 -0
  101. mcp_proxy_adapter/examples/generate_all_certificates.py +429 -0
  102. mcp_proxy_adapter/examples/generate_certificates.py +121 -0
  103. mcp_proxy_adapter/examples/keys/ca_key.pem +28 -0
  104. mcp_proxy_adapter/examples/keys/mcp_proxy_adapter_ca_ca.key +28 -0
  105. mcp_proxy_adapter/examples/keys/test_ca_ca.key +28 -0
  106. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log +220 -0
  107. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.1 +1 -0
  108. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.2 +1 -0
  109. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.3 +1 -0
  110. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.4 +1 -0
  111. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.5 +1 -0
  112. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log +220 -0
  113. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.1 +1 -0
  114. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.2 +1 -0
  115. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.3 +1 -0
  116. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.4 +1 -0
  117. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.5 +1 -0
  118. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log +2 -0
  119. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.1 +1 -0
  120. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.2 +1 -0
  121. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.3 +1 -0
  122. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.4 +1 -0
  123. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.5 +1 -0
  124. mcp_proxy_adapter/examples/proxy_registration_example.py +401 -0
  125. mcp_proxy_adapter/examples/roles.json +38 -0
  126. mcp_proxy_adapter/examples/run_example.py +81 -0
  127. mcp_proxy_adapter/examples/run_security_tests.py +326 -0
  128. mcp_proxy_adapter/examples/run_security_tests_fixed.py +300 -0
  129. mcp_proxy_adapter/examples/security_test_client.py +743 -0
  130. mcp_proxy_adapter/examples/server_configs/config_basic_http.json +204 -0
  131. mcp_proxy_adapter/examples/server_configs/config_http_token.json +238 -0
  132. mcp_proxy_adapter/examples/server_configs/config_https.json +215 -0
  133. mcp_proxy_adapter/examples/server_configs/config_https_token.json +231 -0
  134. mcp_proxy_adapter/examples/server_configs/config_mtls.json +215 -0
  135. mcp_proxy_adapter/examples/server_configs/config_proxy_registration.json +250 -0
  136. mcp_proxy_adapter/examples/server_configs/config_simple.json +46 -0
  137. mcp_proxy_adapter/examples/server_configs/roles.json +38 -0
  138. mcp_proxy_adapter/examples/test_examples.py +344 -0
  139. mcp_proxy_adapter/examples/universal_client.py +628 -0
  140. mcp_proxy_adapter/main.py +21 -10
  141. mcp_proxy_adapter/utils/config_generator.py +639 -0
  142. mcp_proxy_adapter/version.py +2 -1
  143. mcp_proxy_adapter-6.1.0.dist-info/METADATA +205 -0
  144. mcp_proxy_adapter-6.1.0.dist-info/RECORD +193 -0
  145. mcp_proxy_adapter-6.1.0.dist-info/entry_points.txt +2 -0
  146. {mcp_proxy_adapter-6.0.0.dist-info → mcp_proxy_adapter-6.1.0.dist-info}/licenses/LICENSE +2 -2
  147. mcp_proxy_adapter/api/middleware/auth.py +0 -146
  148. mcp_proxy_adapter/api/middleware/auth_adapter.py +0 -235
  149. mcp_proxy_adapter/api/middleware/mtls_adapter.py +0 -305
  150. mcp_proxy_adapter/api/middleware/mtls_middleware.py +0 -296
  151. mcp_proxy_adapter/api/middleware/rate_limit.py +0 -152
  152. mcp_proxy_adapter/api/middleware/rate_limit_adapter.py +0 -241
  153. mcp_proxy_adapter/api/middleware/roles_adapter.py +0 -365
  154. mcp_proxy_adapter/api/middleware/roles_middleware.py +0 -381
  155. mcp_proxy_adapter/api/middleware/security.py +0 -376
  156. mcp_proxy_adapter/api/middleware/token_auth_middleware.py +0 -261
  157. mcp_proxy_adapter/examples/__init__.py +0 -7
  158. mcp_proxy_adapter/examples/basic_server/README.md +0 -60
  159. mcp_proxy_adapter/examples/basic_server/__init__.py +0 -7
  160. mcp_proxy_adapter/examples/basic_server/basic_custom_settings.json +0 -39
  161. mcp_proxy_adapter/examples/basic_server/config.json +0 -70
  162. mcp_proxy_adapter/examples/basic_server/config_all_protocols.json +0 -54
  163. mcp_proxy_adapter/examples/basic_server/config_http.json +0 -70
  164. mcp_proxy_adapter/examples/basic_server/config_http_only.json +0 -52
  165. mcp_proxy_adapter/examples/basic_server/config_https.json +0 -58
  166. mcp_proxy_adapter/examples/basic_server/config_mtls.json +0 -58
  167. mcp_proxy_adapter/examples/basic_server/config_ssl.json +0 -46
  168. mcp_proxy_adapter/examples/basic_server/custom_settings_example.py +0 -238
  169. mcp_proxy_adapter/examples/basic_server/server.py +0 -114
  170. mcp_proxy_adapter/examples/custom_commands/README.md +0 -127
  171. mcp_proxy_adapter/examples/custom_commands/__init__.py +0 -27
  172. mcp_proxy_adapter/examples/custom_commands/advanced_hooks.py +0 -566
  173. mcp_proxy_adapter/examples/custom_commands/auto_commands/__init__.py +0 -6
  174. mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_echo_command.py +0 -103
  175. mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_info_command.py +0 -111
  176. mcp_proxy_adapter/examples/custom_commands/auto_commands/test_command.py +0 -105
  177. mcp_proxy_adapter/examples/custom_commands/catalog/commands/test_command.py +0 -129
  178. mcp_proxy_adapter/examples/custom_commands/config.json +0 -118
  179. mcp_proxy_adapter/examples/custom_commands/config_all_protocols.json +0 -46
  180. mcp_proxy_adapter/examples/custom_commands/config_https_only.json +0 -46
  181. mcp_proxy_adapter/examples/custom_commands/config_https_transport.json +0 -33
  182. mcp_proxy_adapter/examples/custom_commands/config_mtls_only.json +0 -46
  183. mcp_proxy_adapter/examples/custom_commands/config_mtls_transport.json +0 -33
  184. mcp_proxy_adapter/examples/custom_commands/config_single_transport.json +0 -33
  185. mcp_proxy_adapter/examples/custom_commands/custom_health_command.py +0 -169
  186. mcp_proxy_adapter/examples/custom_commands/custom_help_command.py +0 -215
  187. mcp_proxy_adapter/examples/custom_commands/custom_openapi_generator.py +0 -76
  188. mcp_proxy_adapter/examples/custom_commands/custom_settings.json +0 -96
  189. mcp_proxy_adapter/examples/custom_commands/custom_settings_manager.py +0 -241
  190. mcp_proxy_adapter/examples/custom_commands/data_transform_command.py +0 -135
  191. mcp_proxy_adapter/examples/custom_commands/echo_command.py +0 -122
  192. mcp_proxy_adapter/examples/custom_commands/full_help_response.json +0 -1
  193. mcp_proxy_adapter/examples/custom_commands/generated_openapi.json +0 -629
  194. mcp_proxy_adapter/examples/custom_commands/get_openapi.py +0 -103
  195. mcp_proxy_adapter/examples/custom_commands/hooks.py +0 -230
  196. mcp_proxy_adapter/examples/custom_commands/intercept_command.py +0 -123
  197. mcp_proxy_adapter/examples/custom_commands/loadable_commands/test_ignored.py +0 -129
  198. mcp_proxy_adapter/examples/custom_commands/manual_echo_command.py +0 -103
  199. mcp_proxy_adapter/examples/custom_commands/proxy_connection_manager.py +0 -278
  200. mcp_proxy_adapter/examples/custom_commands/server.py +0 -252
  201. mcp_proxy_adapter/examples/custom_commands/simple_openapi_server.py +0 -75
  202. mcp_proxy_adapter/examples/custom_commands/start_server_with_proxy_manager.py +0 -299
  203. mcp_proxy_adapter/examples/custom_commands/start_server_with_registration.py +0 -278
  204. mcp_proxy_adapter/examples/custom_commands/test_hooks.py +0 -176
  205. mcp_proxy_adapter/examples/custom_commands/test_openapi.py +0 -27
  206. mcp_proxy_adapter/examples/custom_commands/test_registry.py +0 -23
  207. mcp_proxy_adapter/examples/custom_commands/test_simple.py +0 -19
  208. mcp_proxy_adapter/examples/custom_project_example/README.md +0 -103
  209. mcp_proxy_adapter/examples/custom_project_example/README_EN.md +0 -103
  210. mcp_proxy_adapter/examples/deployment/README.md +0 -49
  211. mcp_proxy_adapter/examples/deployment/__init__.py +0 -7
  212. mcp_proxy_adapter/examples/deployment/config.development.json +0 -8
  213. mcp_proxy_adapter/examples/deployment/config.json +0 -29
  214. mcp_proxy_adapter/examples/deployment/config.production.json +0 -12
  215. mcp_proxy_adapter/examples/deployment/config.staging.json +0 -11
  216. mcp_proxy_adapter/examples/deployment/docker-compose.yml +0 -31
  217. mcp_proxy_adapter/examples/deployment/run.sh +0 -43
  218. mcp_proxy_adapter/examples/deployment/run_docker.sh +0 -84
  219. mcp_proxy_adapter/examples/simple_custom_commands/README.md +0 -149
  220. mcp_proxy_adapter/examples/simple_custom_commands/README_EN.md +0 -149
  221. mcp_proxy_adapter/schemas/base_schema.json +0 -114
  222. mcp_proxy_adapter/schemas/openapi_schema.json +0 -314
  223. mcp_proxy_adapter/schemas/roles_schema.json +0 -162
  224. mcp_proxy_adapter/tests/__init__.py +0 -0
  225. mcp_proxy_adapter/tests/api/__init__.py +0 -3
  226. mcp_proxy_adapter/tests/api/test_cmd_endpoint.py +0 -115
  227. mcp_proxy_adapter/tests/api/test_custom_openapi.py +0 -617
  228. mcp_proxy_adapter/tests/api/test_handlers.py +0 -522
  229. mcp_proxy_adapter/tests/api/test_middleware.py +0 -340
  230. mcp_proxy_adapter/tests/api/test_schemas.py +0 -546
  231. mcp_proxy_adapter/tests/api/test_tool_integration.py +0 -531
  232. mcp_proxy_adapter/tests/commands/__init__.py +0 -3
  233. mcp_proxy_adapter/tests/commands/test_config_command.py +0 -211
  234. mcp_proxy_adapter/tests/commands/test_echo_command.py +0 -127
  235. mcp_proxy_adapter/tests/commands/test_help_command.py +0 -136
  236. mcp_proxy_adapter/tests/conftest.py +0 -131
  237. mcp_proxy_adapter/tests/functional/__init__.py +0 -3
  238. mcp_proxy_adapter/tests/functional/test_api.py +0 -253
  239. mcp_proxy_adapter/tests/integration/__init__.py +0 -3
  240. mcp_proxy_adapter/tests/integration/test_cmd_integration.py +0 -129
  241. mcp_proxy_adapter/tests/integration/test_integration.py +0 -255
  242. mcp_proxy_adapter/tests/performance/__init__.py +0 -3
  243. mcp_proxy_adapter/tests/performance/test_performance.py +0 -189
  244. mcp_proxy_adapter/tests/stubs/__init__.py +0 -10
  245. mcp_proxy_adapter/tests/stubs/echo_command.py +0 -104
  246. mcp_proxy_adapter/tests/test_api_endpoints.py +0 -271
  247. mcp_proxy_adapter/tests/test_api_handlers.py +0 -289
  248. mcp_proxy_adapter/tests/test_base_command.py +0 -123
  249. mcp_proxy_adapter/tests/test_batch_requests.py +0 -117
  250. mcp_proxy_adapter/tests/test_command_registry.py +0 -281
  251. mcp_proxy_adapter/tests/test_config.py +0 -127
  252. mcp_proxy_adapter/tests/test_utils.py +0 -65
  253. mcp_proxy_adapter/tests/unit/__init__.py +0 -3
  254. mcp_proxy_adapter/tests/unit/test_base_command.py +0 -436
  255. mcp_proxy_adapter/tests/unit/test_config.py +0 -270
  256. mcp_proxy_adapter-6.0.0.dist-info/METADATA +0 -201
  257. mcp_proxy_adapter-6.0.0.dist-info/RECORD +0 -179
  258. {mcp_proxy_adapter-6.0.0.dist-info → mcp_proxy_adapter-6.1.0.dist-info}/WHEEL +0 -0
  259. {mcp_proxy_adapter-6.0.0.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())