mcp-proxy-adapter 6.0.0__py3-none-any.whl → 6.1.1__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 (264) 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 +9 -4
  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/protocol_middleware.py +32 -13
  7. mcp_proxy_adapter/api/middleware/unified_security.py +160 -0
  8. mcp_proxy_adapter/api/middleware/user_info_middleware.py +83 -0
  9. mcp_proxy_adapter/commands/__init__.py +7 -1
  10. mcp_proxy_adapter/commands/base.py +7 -4
  11. mcp_proxy_adapter/commands/builtin_commands.py +8 -2
  12. mcp_proxy_adapter/commands/command_registry.py +8 -0
  13. mcp_proxy_adapter/commands/echo_command.py +81 -0
  14. mcp_proxy_adapter/commands/help_command.py +21 -14
  15. mcp_proxy_adapter/commands/proxy_registration_command.py +326 -185
  16. mcp_proxy_adapter/commands/role_test_command.py +141 -0
  17. mcp_proxy_adapter/commands/security_command.py +488 -0
  18. mcp_proxy_adapter/commands/ssl_setup_command.py +2 -2
  19. mcp_proxy_adapter/commands/token_management_command.py +1 -1
  20. mcp_proxy_adapter/config.py +81 -21
  21. mcp_proxy_adapter/core/app_factory.py +326 -0
  22. mcp_proxy_adapter/core/client_security.py +384 -0
  23. mcp_proxy_adapter/core/logging.py +8 -3
  24. mcp_proxy_adapter/core/mtls_asgi.py +156 -0
  25. mcp_proxy_adapter/core/mtls_asgi_app.py +187 -0
  26. mcp_proxy_adapter/core/protocol_manager.py +139 -8
  27. mcp_proxy_adapter/core/proxy_client.py +602 -0
  28. mcp_proxy_adapter/core/proxy_registration.py +299 -47
  29. mcp_proxy_adapter/core/security_adapter.py +12 -15
  30. mcp_proxy_adapter/core/security_integration.py +285 -0
  31. mcp_proxy_adapter/core/server_adapter.py +345 -0
  32. mcp_proxy_adapter/core/server_engine.py +364 -0
  33. mcp_proxy_adapter/core/unified_config_adapter.py +579 -0
  34. mcp_proxy_adapter/docs/EN/TROUBLESHOOTING.md +285 -0
  35. mcp_proxy_adapter/docs/RU/TROUBLESHOOTING.md +285 -0
  36. mcp_proxy_adapter/examples/README.md +230 -97
  37. mcp_proxy_adapter/examples/README_EN.md +258 -0
  38. mcp_proxy_adapter/examples/SECURITY_TESTING.md +455 -0
  39. mcp_proxy_adapter/examples/basic_framework/configs/http_auth.json +37 -0
  40. mcp_proxy_adapter/examples/basic_framework/configs/http_simple.json +23 -0
  41. mcp_proxy_adapter/examples/basic_framework/configs/https_auth.json +43 -0
  42. mcp_proxy_adapter/examples/basic_framework/configs/https_no_protocol_middleware.json +36 -0
  43. mcp_proxy_adapter/examples/basic_framework/configs/https_simple.json +29 -0
  44. mcp_proxy_adapter/examples/basic_framework/configs/mtls_no_protocol_middleware.json +34 -0
  45. mcp_proxy_adapter/examples/basic_framework/configs/mtls_no_roles.json +39 -0
  46. mcp_proxy_adapter/examples/basic_framework/configs/mtls_simple.json +35 -0
  47. mcp_proxy_adapter/examples/basic_framework/configs/mtls_with_roles.json +45 -0
  48. mcp_proxy_adapter/examples/basic_framework/main.py +63 -0
  49. mcp_proxy_adapter/examples/basic_framework/roles.json +21 -0
  50. mcp_proxy_adapter/examples/cert_config.json +9 -0
  51. mcp_proxy_adapter/examples/certs/admin.crt +32 -0
  52. mcp_proxy_adapter/examples/certs/admin.key +52 -0
  53. mcp_proxy_adapter/examples/certs/admin_cert.pem +21 -0
  54. mcp_proxy_adapter/examples/certs/admin_key.pem +28 -0
  55. mcp_proxy_adapter/examples/certs/ca_cert.pem +23 -0
  56. mcp_proxy_adapter/examples/certs/ca_cert.srl +1 -0
  57. mcp_proxy_adapter/examples/certs/ca_key.pem +28 -0
  58. mcp_proxy_adapter/examples/certs/cert_config.json +9 -0
  59. mcp_proxy_adapter/examples/certs/client.crt +32 -0
  60. mcp_proxy_adapter/examples/certs/client.key +52 -0
  61. mcp_proxy_adapter/examples/certs/client_admin.crt +32 -0
  62. mcp_proxy_adapter/examples/certs/client_admin.key +52 -0
  63. mcp_proxy_adapter/examples/certs/client_user.crt +32 -0
  64. mcp_proxy_adapter/examples/certs/client_user.key +52 -0
  65. mcp_proxy_adapter/examples/certs/guest_cert.pem +21 -0
  66. mcp_proxy_adapter/examples/certs/guest_key.pem +28 -0
  67. mcp_proxy_adapter/examples/certs/mcp_proxy_adapter_ca_ca.crt +23 -0
  68. mcp_proxy_adapter/examples/certs/proxy_cert.pem +21 -0
  69. mcp_proxy_adapter/examples/certs/proxy_key.pem +28 -0
  70. mcp_proxy_adapter/examples/certs/readonly.crt +32 -0
  71. mcp_proxy_adapter/examples/certs/readonly.key +52 -0
  72. mcp_proxy_adapter/examples/certs/readonly_cert.pem +21 -0
  73. mcp_proxy_adapter/examples/certs/readonly_key.pem +28 -0
  74. mcp_proxy_adapter/examples/certs/server.crt +32 -0
  75. mcp_proxy_adapter/examples/certs/server.key +52 -0
  76. mcp_proxy_adapter/examples/certs/server_cert.pem +32 -0
  77. mcp_proxy_adapter/examples/certs/server_key.pem +52 -0
  78. mcp_proxy_adapter/examples/certs/test_ca_ca.crt +20 -0
  79. mcp_proxy_adapter/examples/certs/user.crt +32 -0
  80. mcp_proxy_adapter/examples/certs/user.key +52 -0
  81. mcp_proxy_adapter/examples/certs/user_cert.pem +21 -0
  82. mcp_proxy_adapter/examples/certs/user_key.pem +28 -0
  83. mcp_proxy_adapter/examples/client_configs/api_key_client.json +13 -0
  84. mcp_proxy_adapter/examples/client_configs/basic_auth_client.json +13 -0
  85. mcp_proxy_adapter/examples/client_configs/certificate_client.json +22 -0
  86. mcp_proxy_adapter/examples/client_configs/jwt_client.json +15 -0
  87. mcp_proxy_adapter/examples/client_configs/no_auth_client.json +9 -0
  88. mcp_proxy_adapter/examples/commands/__init__.py +1 -0
  89. mcp_proxy_adapter/examples/create_certificates_simple.py +307 -0
  90. mcp_proxy_adapter/examples/debug_request_state.py +144 -0
  91. mcp_proxy_adapter/examples/debug_role_chain.py +205 -0
  92. mcp_proxy_adapter/examples/demo_client.py +341 -0
  93. mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +99 -0
  94. mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +106 -0
  95. mcp_proxy_adapter/examples/full_application/configs/http_auth.json +37 -0
  96. mcp_proxy_adapter/examples/full_application/configs/http_simple.json +23 -0
  97. mcp_proxy_adapter/examples/full_application/configs/https_auth.json +39 -0
  98. mcp_proxy_adapter/examples/full_application/configs/https_simple.json +25 -0
  99. mcp_proxy_adapter/examples/full_application/configs/mtls_no_roles.json +39 -0
  100. mcp_proxy_adapter/examples/full_application/configs/mtls_with_roles.json +45 -0
  101. mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +97 -0
  102. mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +95 -0
  103. mcp_proxy_adapter/examples/full_application/main.py +138 -0
  104. mcp_proxy_adapter/examples/full_application/roles.json +21 -0
  105. mcp_proxy_adapter/examples/generate_all_certificates.py +429 -0
  106. mcp_proxy_adapter/examples/generate_certificates.py +121 -0
  107. mcp_proxy_adapter/examples/keys/ca_key.pem +28 -0
  108. mcp_proxy_adapter/examples/keys/mcp_proxy_adapter_ca_ca.key +28 -0
  109. mcp_proxy_adapter/examples/keys/test_ca_ca.key +28 -0
  110. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log +220 -0
  111. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.1 +1 -0
  112. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.2 +1 -0
  113. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.3 +1 -0
  114. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.4 +1 -0
  115. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.5 +1 -0
  116. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log +220 -0
  117. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.1 +1 -0
  118. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.2 +1 -0
  119. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.3 +1 -0
  120. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.4 +1 -0
  121. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.5 +1 -0
  122. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log +2 -0
  123. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.1 +1 -0
  124. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.2 +1 -0
  125. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.3 +1 -0
  126. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.4 +1 -0
  127. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.5 +1 -0
  128. mcp_proxy_adapter/examples/proxy_registration_example.py +401 -0
  129. mcp_proxy_adapter/examples/roles.json +38 -0
  130. mcp_proxy_adapter/examples/run_example.py +81 -0
  131. mcp_proxy_adapter/examples/run_security_tests.py +326 -0
  132. mcp_proxy_adapter/examples/run_security_tests_fixed.py +300 -0
  133. mcp_proxy_adapter/examples/security_test_client.py +743 -0
  134. mcp_proxy_adapter/examples/server_configs/config_basic_http.json +204 -0
  135. mcp_proxy_adapter/examples/server_configs/config_http_token.json +238 -0
  136. mcp_proxy_adapter/examples/server_configs/config_https.json +215 -0
  137. mcp_proxy_adapter/examples/server_configs/config_https_token.json +231 -0
  138. mcp_proxy_adapter/examples/server_configs/config_mtls.json +215 -0
  139. mcp_proxy_adapter/examples/server_configs/config_proxy_registration.json +250 -0
  140. mcp_proxy_adapter/examples/server_configs/config_simple.json +46 -0
  141. mcp_proxy_adapter/examples/server_configs/roles.json +38 -0
  142. mcp_proxy_adapter/examples/test_config_generator.py +110 -0
  143. mcp_proxy_adapter/examples/test_examples.py +344 -0
  144. mcp_proxy_adapter/examples/universal_client.py +628 -0
  145. mcp_proxy_adapter/main.py +21 -10
  146. mcp_proxy_adapter/utils/config_generator.py +727 -0
  147. mcp_proxy_adapter/version.py +5 -2
  148. mcp_proxy_adapter-6.1.1.dist-info/METADATA +205 -0
  149. mcp_proxy_adapter-6.1.1.dist-info/RECORD +197 -0
  150. mcp_proxy_adapter-6.1.1.dist-info/entry_points.txt +2 -0
  151. {mcp_proxy_adapter-6.0.0.dist-info → mcp_proxy_adapter-6.1.1.dist-info}/licenses/LICENSE +2 -2
  152. mcp_proxy_adapter/api/middleware/auth.py +0 -146
  153. mcp_proxy_adapter/api/middleware/auth_adapter.py +0 -235
  154. mcp_proxy_adapter/api/middleware/mtls_adapter.py +0 -305
  155. mcp_proxy_adapter/api/middleware/mtls_middleware.py +0 -296
  156. mcp_proxy_adapter/api/middleware/rate_limit.py +0 -152
  157. mcp_proxy_adapter/api/middleware/rate_limit_adapter.py +0 -241
  158. mcp_proxy_adapter/api/middleware/roles_adapter.py +0 -365
  159. mcp_proxy_adapter/api/middleware/roles_middleware.py +0 -381
  160. mcp_proxy_adapter/api/middleware/security.py +0 -376
  161. mcp_proxy_adapter/api/middleware/token_auth_middleware.py +0 -261
  162. mcp_proxy_adapter/examples/__init__.py +0 -7
  163. mcp_proxy_adapter/examples/basic_server/README.md +0 -60
  164. mcp_proxy_adapter/examples/basic_server/__init__.py +0 -7
  165. mcp_proxy_adapter/examples/basic_server/basic_custom_settings.json +0 -39
  166. mcp_proxy_adapter/examples/basic_server/config.json +0 -70
  167. mcp_proxy_adapter/examples/basic_server/config_all_protocols.json +0 -54
  168. mcp_proxy_adapter/examples/basic_server/config_http.json +0 -70
  169. mcp_proxy_adapter/examples/basic_server/config_http_only.json +0 -52
  170. mcp_proxy_adapter/examples/basic_server/config_https.json +0 -58
  171. mcp_proxy_adapter/examples/basic_server/config_mtls.json +0 -58
  172. mcp_proxy_adapter/examples/basic_server/config_ssl.json +0 -46
  173. mcp_proxy_adapter/examples/basic_server/custom_settings_example.py +0 -238
  174. mcp_proxy_adapter/examples/basic_server/server.py +0 -114
  175. mcp_proxy_adapter/examples/custom_commands/README.md +0 -127
  176. mcp_proxy_adapter/examples/custom_commands/__init__.py +0 -27
  177. mcp_proxy_adapter/examples/custom_commands/advanced_hooks.py +0 -566
  178. mcp_proxy_adapter/examples/custom_commands/auto_commands/__init__.py +0 -6
  179. mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_echo_command.py +0 -103
  180. mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_info_command.py +0 -111
  181. mcp_proxy_adapter/examples/custom_commands/auto_commands/test_command.py +0 -105
  182. mcp_proxy_adapter/examples/custom_commands/catalog/commands/test_command.py +0 -129
  183. mcp_proxy_adapter/examples/custom_commands/config.json +0 -118
  184. mcp_proxy_adapter/examples/custom_commands/config_all_protocols.json +0 -46
  185. mcp_proxy_adapter/examples/custom_commands/config_https_only.json +0 -46
  186. mcp_proxy_adapter/examples/custom_commands/config_https_transport.json +0 -33
  187. mcp_proxy_adapter/examples/custom_commands/config_mtls_only.json +0 -46
  188. mcp_proxy_adapter/examples/custom_commands/config_mtls_transport.json +0 -33
  189. mcp_proxy_adapter/examples/custom_commands/config_single_transport.json +0 -33
  190. mcp_proxy_adapter/examples/custom_commands/custom_health_command.py +0 -169
  191. mcp_proxy_adapter/examples/custom_commands/custom_help_command.py +0 -215
  192. mcp_proxy_adapter/examples/custom_commands/custom_openapi_generator.py +0 -76
  193. mcp_proxy_adapter/examples/custom_commands/custom_settings.json +0 -96
  194. mcp_proxy_adapter/examples/custom_commands/custom_settings_manager.py +0 -241
  195. mcp_proxy_adapter/examples/custom_commands/data_transform_command.py +0 -135
  196. mcp_proxy_adapter/examples/custom_commands/echo_command.py +0 -122
  197. mcp_proxy_adapter/examples/custom_commands/full_help_response.json +0 -1
  198. mcp_proxy_adapter/examples/custom_commands/generated_openapi.json +0 -629
  199. mcp_proxy_adapter/examples/custom_commands/get_openapi.py +0 -103
  200. mcp_proxy_adapter/examples/custom_commands/hooks.py +0 -230
  201. mcp_proxy_adapter/examples/custom_commands/intercept_command.py +0 -123
  202. mcp_proxy_adapter/examples/custom_commands/loadable_commands/test_ignored.py +0 -129
  203. mcp_proxy_adapter/examples/custom_commands/manual_echo_command.py +0 -103
  204. mcp_proxy_adapter/examples/custom_commands/proxy_connection_manager.py +0 -278
  205. mcp_proxy_adapter/examples/custom_commands/server.py +0 -252
  206. mcp_proxy_adapter/examples/custom_commands/simple_openapi_server.py +0 -75
  207. mcp_proxy_adapter/examples/custom_commands/start_server_with_proxy_manager.py +0 -299
  208. mcp_proxy_adapter/examples/custom_commands/start_server_with_registration.py +0 -278
  209. mcp_proxy_adapter/examples/custom_commands/test_hooks.py +0 -176
  210. mcp_proxy_adapter/examples/custom_commands/test_openapi.py +0 -27
  211. mcp_proxy_adapter/examples/custom_commands/test_registry.py +0 -23
  212. mcp_proxy_adapter/examples/custom_commands/test_simple.py +0 -19
  213. mcp_proxy_adapter/examples/custom_project_example/README.md +0 -103
  214. mcp_proxy_adapter/examples/custom_project_example/README_EN.md +0 -103
  215. mcp_proxy_adapter/examples/deployment/README.md +0 -49
  216. mcp_proxy_adapter/examples/deployment/__init__.py +0 -7
  217. mcp_proxy_adapter/examples/deployment/config.development.json +0 -8
  218. mcp_proxy_adapter/examples/deployment/config.json +0 -29
  219. mcp_proxy_adapter/examples/deployment/config.production.json +0 -12
  220. mcp_proxy_adapter/examples/deployment/config.staging.json +0 -11
  221. mcp_proxy_adapter/examples/deployment/docker-compose.yml +0 -31
  222. mcp_proxy_adapter/examples/deployment/run.sh +0 -43
  223. mcp_proxy_adapter/examples/deployment/run_docker.sh +0 -84
  224. mcp_proxy_adapter/examples/simple_custom_commands/README.md +0 -149
  225. mcp_proxy_adapter/examples/simple_custom_commands/README_EN.md +0 -149
  226. mcp_proxy_adapter/schemas/base_schema.json +0 -114
  227. mcp_proxy_adapter/schemas/openapi_schema.json +0 -314
  228. mcp_proxy_adapter/schemas/roles_schema.json +0 -162
  229. mcp_proxy_adapter/tests/__init__.py +0 -0
  230. mcp_proxy_adapter/tests/api/__init__.py +0 -3
  231. mcp_proxy_adapter/tests/api/test_cmd_endpoint.py +0 -115
  232. mcp_proxy_adapter/tests/api/test_custom_openapi.py +0 -617
  233. mcp_proxy_adapter/tests/api/test_handlers.py +0 -522
  234. mcp_proxy_adapter/tests/api/test_middleware.py +0 -340
  235. mcp_proxy_adapter/tests/api/test_schemas.py +0 -546
  236. mcp_proxy_adapter/tests/api/test_tool_integration.py +0 -531
  237. mcp_proxy_adapter/tests/commands/__init__.py +0 -3
  238. mcp_proxy_adapter/tests/commands/test_config_command.py +0 -211
  239. mcp_proxy_adapter/tests/commands/test_echo_command.py +0 -127
  240. mcp_proxy_adapter/tests/commands/test_help_command.py +0 -136
  241. mcp_proxy_adapter/tests/conftest.py +0 -131
  242. mcp_proxy_adapter/tests/functional/__init__.py +0 -3
  243. mcp_proxy_adapter/tests/functional/test_api.py +0 -253
  244. mcp_proxy_adapter/tests/integration/__init__.py +0 -3
  245. mcp_proxy_adapter/tests/integration/test_cmd_integration.py +0 -129
  246. mcp_proxy_adapter/tests/integration/test_integration.py +0 -255
  247. mcp_proxy_adapter/tests/performance/__init__.py +0 -3
  248. mcp_proxy_adapter/tests/performance/test_performance.py +0 -189
  249. mcp_proxy_adapter/tests/stubs/__init__.py +0 -10
  250. mcp_proxy_adapter/tests/stubs/echo_command.py +0 -104
  251. mcp_proxy_adapter/tests/test_api_endpoints.py +0 -271
  252. mcp_proxy_adapter/tests/test_api_handlers.py +0 -289
  253. mcp_proxy_adapter/tests/test_base_command.py +0 -123
  254. mcp_proxy_adapter/tests/test_batch_requests.py +0 -117
  255. mcp_proxy_adapter/tests/test_command_registry.py +0 -281
  256. mcp_proxy_adapter/tests/test_config.py +0 -127
  257. mcp_proxy_adapter/tests/test_utils.py +0 -65
  258. mcp_proxy_adapter/tests/unit/__init__.py +0 -3
  259. mcp_proxy_adapter/tests/unit/test_base_command.py +0 -436
  260. mcp_proxy_adapter/tests/unit/test_config.py +0 -270
  261. mcp_proxy_adapter-6.0.0.dist-info/METADATA +0 -201
  262. mcp_proxy_adapter-6.0.0.dist-info/RECORD +0 -179
  263. {mcp_proxy_adapter-6.0.0.dist-info → mcp_proxy_adapter-6.1.1.dist-info}/WHEEL +0 -0
  264. {mcp_proxy_adapter-6.0.0.dist-info → mcp_proxy_adapter-6.1.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,579 @@
1
+ """
2
+ Unified Configuration Adapter for mcp_security_framework integration.
3
+
4
+ Author: Vasiliy Zdanovskiy
5
+ email: vasilyvz@gmail.com
6
+
7
+ This module provides a unified adapter for converting mcp_proxy_adapter configuration
8
+ to SecurityConfig format used by mcp_security_framework.
9
+ """
10
+
11
+ import json
12
+ import logging
13
+ from typing import Dict, Any, Optional, List, Union
14
+ from pathlib import Path
15
+ from dataclasses import dataclass
16
+
17
+ # Import mcp_security_framework components
18
+ try:
19
+ from mcp_security_framework import SecurityConfig
20
+ from mcp_security_framework.schemas.config import (
21
+ AuthConfig, SSLConfig, PermissionConfig, RateLimitConfig
22
+ )
23
+ SECURITY_FRAMEWORK_AVAILABLE = True
24
+ except ImportError:
25
+ SECURITY_FRAMEWORK_AVAILABLE = False
26
+ SecurityConfig = None
27
+ AuthConfig = None
28
+ SSLConfig = None
29
+ PermissionConfig = None
30
+ RateLimitConfig = None
31
+
32
+ from mcp_proxy_adapter.core.logging import logger
33
+
34
+
35
+ @dataclass
36
+ class ValidationResult:
37
+ """Result of configuration validation."""
38
+
39
+ is_valid: bool
40
+ errors: List[str]
41
+ warnings: List[str]
42
+ details: Dict[str, Any]
43
+
44
+ def __post_init__(self):
45
+ """Initialize with empty lists if None."""
46
+ if self.errors is None:
47
+ self.errors = []
48
+ if self.warnings is None:
49
+ self.warnings = []
50
+ if self.details is None:
51
+ self.details = {}
52
+
53
+
54
+ class UnifiedConfigAdapter:
55
+ """
56
+ Unified adapter for converting mcp_proxy_adapter configuration to SecurityConfig.
57
+
58
+ This adapter handles:
59
+ - Legacy configuration format compatibility
60
+ - Configuration validation
61
+ - Conversion to mcp_security_framework format
62
+ - Default value management
63
+ """
64
+
65
+ def __init__(self):
66
+ """Initialize the unified configuration adapter."""
67
+ self.validation_errors = []
68
+ self.validation_warnings = []
69
+
70
+ def convert_to_security_config(self, config: Dict[str, Any]) -> Optional[SecurityConfig]:
71
+ """
72
+ Convert mcp_proxy_adapter configuration to SecurityConfig.
73
+
74
+ Args:
75
+ config: mcp_proxy_adapter configuration dictionary
76
+
77
+ Returns:
78
+ SecurityConfig instance or None if conversion failed
79
+ """
80
+ if not SECURITY_FRAMEWORK_AVAILABLE:
81
+ logger.error("mcp_security_framework not available, cannot convert configuration")
82
+ return None
83
+
84
+ try:
85
+ # Validate configuration first
86
+ validation_result = self.validate_configuration(config)
87
+ if not validation_result.is_valid:
88
+ logger.error(f"Configuration validation failed: {validation_result.errors}")
89
+ return None
90
+
91
+ # Convert configuration sections
92
+ auth_config = self._convert_auth_config(config)
93
+ ssl_config = self._convert_ssl_config(config)
94
+ permission_config = self._convert_permission_config(config)
95
+ rate_limit_config = self._convert_rate_limit_config(config)
96
+
97
+ # Create SecurityConfig
98
+ security_config = SecurityConfig(
99
+ auth=auth_config,
100
+ ssl=ssl_config,
101
+ permissions=permission_config,
102
+ rate_limit=rate_limit_config
103
+ )
104
+
105
+ logger.info("Configuration successfully converted to SecurityConfig")
106
+ return security_config
107
+
108
+ except Exception as e:
109
+ logger.error(f"Failed to convert configuration: {e}")
110
+ return None
111
+
112
+ def validate_configuration(self, config: Dict[str, Any]) -> ValidationResult:
113
+ """
114
+ Validate configuration before conversion.
115
+
116
+ Args:
117
+ config: Configuration dictionary to validate
118
+
119
+ Returns:
120
+ ValidationResult with validation details
121
+ """
122
+ self.validation_errors = []
123
+ self.validation_warnings = []
124
+
125
+ # Debug: Check SSL config at start of validation
126
+ if "security" in config:
127
+ ssl_config = config["security"].get("ssl", {})
128
+ print(f"🔍 Debug: SSL config at start of validation: enabled={ssl_config.get('enabled', False)}")
129
+
130
+ # Debug: Check if root ssl section exists
131
+ if "ssl" in config:
132
+ print(f"🔍 Debug: Root SSL section found: enabled={config['ssl'].get('enabled', False)}")
133
+
134
+ # Check if config is a dictionary
135
+ if not isinstance(config, dict):
136
+ return ValidationResult(
137
+ is_valid=False,
138
+ errors=["Configuration must be a dictionary"],
139
+ warnings=[],
140
+ details={}
141
+ )
142
+
143
+ # Validate security section
144
+ self._validate_security_section(config)
145
+
146
+ # Validate legacy sections for compatibility
147
+ self._validate_legacy_sections(config)
148
+
149
+ # Check for conflicts
150
+ self._check_configuration_conflicts(config)
151
+
152
+ # Validate individual sections
153
+ self._validate_auth_section(config)
154
+ self._validate_ssl_section(config)
155
+ self._validate_permissions_section(config)
156
+ self._validate_rate_limit_section(config)
157
+
158
+ return ValidationResult(
159
+ is_valid=len(self.validation_errors) == 0,
160
+ errors=self.validation_errors.copy(),
161
+ warnings=self.validation_warnings.copy(),
162
+ details={
163
+ "has_security_section": "security" in config,
164
+ "has_legacy_sections": any(key in config for key in ["ssl", "roles", "auth_enabled"]),
165
+ "total_errors": len(self.validation_errors),
166
+ "total_warnings": len(self.validation_warnings)
167
+ }
168
+ )
169
+
170
+ def _validate_security_section(self, config: Dict[str, Any]):
171
+ """Validate security section."""
172
+ security_config = config.get("security", {})
173
+
174
+ if not isinstance(security_config, dict):
175
+ self.validation_errors.append("Security section must be a dictionary")
176
+ return
177
+
178
+ # Check for unknown keys in security section
179
+ known_keys = {"enabled", "auth", "ssl", "permissions", "rate_limit", "public_paths"}
180
+ unknown_keys = set(security_config.keys()) - known_keys
181
+
182
+ if unknown_keys:
183
+ self.validation_warnings.append(f"Unknown keys in security section: {unknown_keys}")
184
+
185
+ def _validate_legacy_sections(self, config: Dict[str, Any]):
186
+ """Validate legacy configuration sections."""
187
+ legacy_sections = ["ssl", "roles", "auth_enabled", "rate_limit_enabled"]
188
+
189
+ for section in legacy_sections:
190
+ if section in config:
191
+ self.validation_warnings.append(f"Legacy section '{section}' found, consider migrating to security section")
192
+
193
+ def _check_configuration_conflicts(self, config: Dict[str, Any]):
194
+ """Check for configuration conflicts."""
195
+ security_config = config.get("security", {})
196
+
197
+ # Check for SSL configuration conflicts
198
+ if "ssl" in config and "ssl" in security_config:
199
+ self.validation_warnings.append("SSL configuration found in both root and security sections")
200
+
201
+ # Check for auth configuration conflicts
202
+ if "auth_enabled" in config and "auth" in security_config:
203
+ self.validation_warnings.append("Auth configuration found in both root and security sections")
204
+
205
+ def _validate_auth_section(self, config: Dict[str, Any]):
206
+ """Validate authentication section."""
207
+ auth_config = self._get_auth_config(config)
208
+
209
+ if not isinstance(auth_config, dict):
210
+ self.validation_errors.append("Auth configuration must be a dictionary")
211
+ return
212
+
213
+ # Validate auth methods
214
+ methods = auth_config.get("methods", [])
215
+ if not isinstance(methods, list):
216
+ self.validation_errors.append("Auth methods must be a list")
217
+ else:
218
+ valid_methods = {"api_key", "jwt", "certificate"}
219
+ invalid_methods = set(methods) - valid_methods
220
+ if invalid_methods:
221
+ self.validation_errors.append(f"Invalid auth methods: {invalid_methods}")
222
+
223
+ # Validate API keys
224
+ api_keys = auth_config.get("api_keys", {})
225
+ if not isinstance(api_keys, dict):
226
+ self.validation_errors.append("API keys must be a dictionary")
227
+
228
+ # Validate JWT configuration
229
+ if "jwt" in methods:
230
+ jwt_secret = auth_config.get("jwt_secret", "")
231
+ if not jwt_secret:
232
+ self.validation_warnings.append("JWT secret is empty or not set")
233
+
234
+ def _validate_ssl_section(self, config: Dict[str, Any]):
235
+ """Validate SSL section."""
236
+ ssl_config = self._get_ssl_config(config)
237
+
238
+ if not isinstance(ssl_config, dict):
239
+ self.validation_errors.append("SSL configuration must be a dictionary")
240
+ return
241
+
242
+ # Validate certificate files
243
+ if ssl_config.get("enabled", False):
244
+ cert_file = ssl_config.get("cert_file")
245
+ key_file = ssl_config.get("key_file")
246
+
247
+ print(f"🔍 Debug: _validate_ssl_section: cert_file={cert_file}")
248
+ print(f"🔍 Debug: _validate_ssl_section: key_file={key_file}")
249
+ print(f"🔍 Debug: _validate_ssl_section: cert_file exists={Path(cert_file).exists() if cert_file else 'None'}")
250
+ print(f"🔍 Debug: _validate_ssl_section: key_file exists={Path(key_file).exists() if key_file else 'None'}")
251
+
252
+ if cert_file and not Path(cert_file).exists():
253
+ self.validation_warnings.append(f"SSL certificate file not found: {cert_file}")
254
+
255
+ if key_file and not Path(key_file).exists():
256
+ self.validation_warnings.append(f"SSL key file not found: {key_file}")
257
+
258
+ def _validate_permissions_section(self, config: Dict[str, Any]):
259
+ """Validate permissions section."""
260
+ permissions_config = self._get_permissions_config(config)
261
+
262
+ if not isinstance(permissions_config, dict):
263
+ self.validation_errors.append("Permissions configuration must be a dictionary")
264
+ return
265
+
266
+ # Validate roles file
267
+ if permissions_config.get("enabled", False):
268
+ roles_file = permissions_config.get("roles_file")
269
+ if roles_file and not Path(roles_file).exists():
270
+ self.validation_warnings.append(f"Roles file not found: {roles_file}")
271
+
272
+ def _validate_rate_limit_section(self, config: Dict[str, Any]):
273
+ """Validate rate limit section."""
274
+ rate_limit_config = self._get_rate_limit_config(config)
275
+
276
+ if not isinstance(rate_limit_config, dict):
277
+ self.validation_errors.append("Rate limit configuration must be a dictionary")
278
+ return
279
+
280
+ # Validate rate limit values only if enabled
281
+ if rate_limit_config.get("enabled", False):
282
+ requests_per_minute = rate_limit_config.get("requests_per_minute", rate_limit_config.get("default_requests_per_minute", 0))
283
+ if requests_per_minute <= 0:
284
+ self.validation_errors.append("requests_per_minute must be greater than 0 when rate limiting is enabled")
285
+
286
+ def _convert_auth_config(self, config: Dict[str, Any]) -> AuthConfig:
287
+ """Convert authentication configuration."""
288
+ auth_config = self._get_auth_config(config)
289
+
290
+ # Get authentication methods
291
+ methods = auth_config.get("methods", ["api_key"])
292
+
293
+ # Get API keys from multiple sources
294
+ api_keys = auth_config.get("api_keys", {})
295
+ if not api_keys:
296
+ # Try legacy SSL config
297
+ legacy_ssl = config.get("ssl", {})
298
+ api_keys = legacy_ssl.get("api_keys", {})
299
+
300
+ return AuthConfig(
301
+ enabled=auth_config.get("enabled", True),
302
+ methods=methods,
303
+ api_keys=api_keys,
304
+ jwt_secret=auth_config.get("jwt_secret", ""),
305
+ jwt_algorithm=auth_config.get("jwt_algorithm", "HS256"),
306
+ jwt_expiration=auth_config.get("jwt_expiration", 3600)
307
+ )
308
+
309
+ def _convert_ssl_config(self, config: Dict[str, Any]) -> SSLConfig:
310
+ """Convert SSL configuration."""
311
+ ssl_config = self._get_ssl_config(config)
312
+
313
+ return SSLConfig(
314
+ enabled=ssl_config.get("enabled", False),
315
+ cert_file=ssl_config.get("cert_file"),
316
+ key_file=ssl_config.get("key_file"),
317
+ ca_cert=ssl_config.get("ca_cert"),
318
+ min_tls_version=ssl_config.get("min_tls_version", "TLSv1.2"),
319
+ verify_client=ssl_config.get("verify_client", False),
320
+ client_cert_required=ssl_config.get("client_cert_required", False),
321
+ cipher_suites=ssl_config.get("cipher_suites", [])
322
+ )
323
+
324
+ def _convert_permission_config(self, config: Dict[str, Any]) -> PermissionConfig:
325
+ """Convert permissions configuration."""
326
+ permissions_config = self._get_permissions_config(config)
327
+
328
+ return PermissionConfig(
329
+ enabled=permissions_config.get("enabled", True),
330
+ roles_file=permissions_config.get("roles_file", "roles.json"),
331
+ default_role=permissions_config.get("default_role", "user"),
332
+ deny_by_default=permissions_config.get("deny_by_default", True),
333
+ role_mappings=permissions_config.get("role_mappings", {})
334
+ )
335
+
336
+ def _convert_rate_limit_config(self, config: Dict[str, Any]) -> RateLimitConfig:
337
+ """Convert rate limit configuration."""
338
+ rate_limit_config = self._get_rate_limit_config(config)
339
+
340
+ return RateLimitConfig(
341
+ enabled=rate_limit_config.get("enabled", False),
342
+ requests_per_minute=rate_limit_config.get("requests_per_minute", 60),
343
+ requests_per_hour=rate_limit_config.get("requests_per_hour", 1000),
344
+ requests_per_day=rate_limit_config.get("requests_per_day", 10000),
345
+ burst_limit=rate_limit_config.get("burst_limit", 10),
346
+ by_ip=rate_limit_config.get("by_ip", True),
347
+ by_user=rate_limit_config.get("by_user", True),
348
+ by_endpoint=rate_limit_config.get("by_endpoint", False),
349
+ exempt_roles=rate_limit_config.get("exempt_roles", []),
350
+ exempt_endpoints=rate_limit_config.get("exempt_endpoints", [])
351
+ )
352
+
353
+ def _get_auth_config(self, config: Dict[str, Any]) -> Dict[str, Any]:
354
+ """Get authentication configuration from config."""
355
+ security_config = config.get("security", {})
356
+
357
+ # Ensure security_config is a dictionary
358
+ if not isinstance(security_config, dict):
359
+ return {}
360
+
361
+ auth_config = security_config.get("auth", {})
362
+
363
+ # Handle legacy auth_enabled flag
364
+ if config.get("auth_enabled") is not None:
365
+ auth_config["enabled"] = config["auth_enabled"]
366
+
367
+ return auth_config
368
+
369
+ def _get_ssl_config(self, config: Dict[str, Any]) -> Dict[str, Any]:
370
+ """Get SSL configuration from config."""
371
+ security_config = config.get("security", {})
372
+
373
+ # Ensure security_config is a dictionary
374
+ if not isinstance(security_config, dict):
375
+ return {}
376
+
377
+ ssl_config = security_config.get("ssl", {})
378
+
379
+ # Debug: Check SSL config before merging
380
+ print(f"🔍 Debug: _get_ssl_config: security.ssl key_file={ssl_config.get('key_file')}")
381
+
382
+ # Merge with legacy SSL config, but prioritize security.ssl over legacy ssl
383
+ legacy_ssl = config.get("ssl", {})
384
+ if legacy_ssl:
385
+ print(f"🔍 Debug: _get_ssl_config: legacy ssl key_file={legacy_ssl.get('key_file')}")
386
+ # Only merge legacy config if security.ssl is not enabled or missing
387
+ if not ssl_config.get("enabled", False):
388
+ ssl_config.update(legacy_ssl)
389
+ else:
390
+ # If security.ssl is enabled, only merge non-conflicting fields
391
+ for key, value in legacy_ssl.items():
392
+ if key not in ssl_config or ssl_config[key] is None:
393
+ ssl_config[key] = value
394
+
395
+ print(f"🔍 Debug: _get_ssl_config: final key_file={ssl_config.get('key_file')}")
396
+ return ssl_config
397
+
398
+ def _get_permissions_config(self, config: Dict[str, Any]) -> Dict[str, Any]:
399
+ """Get permissions configuration from config."""
400
+ security_config = config.get("security", {})
401
+
402
+ # Ensure security_config is a dictionary
403
+ if not isinstance(security_config, dict):
404
+ return {}
405
+
406
+ permissions_config = security_config.get("permissions", {})
407
+
408
+ # Merge with legacy roles config
409
+ legacy_roles = config.get("roles", {})
410
+ if legacy_roles:
411
+ permissions_config.update(legacy_roles)
412
+
413
+ return permissions_config
414
+
415
+ def _get_rate_limit_config(self, config: Dict[str, Any]) -> Dict[str, Any]:
416
+ """Get rate limit configuration from config."""
417
+ security_config = config.get("security", {})
418
+
419
+ # Ensure security_config is a dictionary
420
+ if not isinstance(security_config, dict):
421
+ return {}
422
+
423
+ rate_limit_config = security_config.get("rate_limit", {})
424
+
425
+ # Handle legacy rate_limit_enabled flag
426
+ if config.get("rate_limit_enabled") is not None:
427
+ rate_limit_config["enabled"] = config["rate_limit_enabled"]
428
+
429
+ return rate_limit_config
430
+
431
+ def get_public_paths(self, config: Dict[str, Any]) -> List[str]:
432
+ """
433
+ Get public paths from configuration.
434
+
435
+ Args:
436
+ config: Configuration dictionary
437
+
438
+ Returns:
439
+ List of public paths
440
+ """
441
+ security_config = config.get("security", {})
442
+
443
+ # Ensure security_config is a dictionary
444
+ if not isinstance(security_config, dict):
445
+ return [
446
+ "/health",
447
+ "/docs",
448
+ "/redoc",
449
+ "/openapi.json",
450
+ "/favicon.ico"
451
+ ]
452
+
453
+ public_paths = security_config.get("public_paths", [])
454
+
455
+ # Add default public paths if none specified
456
+ if not public_paths:
457
+ public_paths = [
458
+ "/health",
459
+ "/docs",
460
+ "/redoc",
461
+ "/openapi.json",
462
+ "/favicon.ico"
463
+ ]
464
+
465
+ return public_paths
466
+
467
+ def get_security_enabled(self, config: Dict[str, Any]) -> bool:
468
+ """
469
+ Check if security is enabled in configuration.
470
+
471
+ Args:
472
+ config: Configuration dictionary
473
+
474
+ Returns:
475
+ True if security is enabled
476
+ """
477
+ security_config = config.get("security", {})
478
+
479
+ # Ensure security_config is a dictionary
480
+ if not isinstance(security_config, dict):
481
+ return True # Default to enabled for safety
482
+
483
+ return security_config.get("enabled", True)
484
+
485
+ def migrate_legacy_config(self, config: Dict[str, Any]) -> Dict[str, Any]:
486
+ """
487
+ Migrate legacy configuration to new format.
488
+
489
+ Args:
490
+ config: Legacy configuration dictionary
491
+
492
+ Returns:
493
+ Migrated configuration dictionary
494
+ """
495
+ migrated_config = config.copy()
496
+
497
+ # Create security section if it doesn't exist
498
+ if "security" not in migrated_config:
499
+ migrated_config["security"] = {}
500
+
501
+ security_config = migrated_config["security"]
502
+
503
+ # Migrate SSL configuration
504
+ if "ssl" in migrated_config and "ssl" not in security_config:
505
+ security_config["ssl"] = migrated_config["ssl"]
506
+ # Don't remove legacy SSL yet for backward compatibility
507
+
508
+ # Migrate roles configuration
509
+ if "roles" in migrated_config and "permissions" not in security_config:
510
+ security_config["permissions"] = migrated_config["roles"]
511
+ # Don't remove legacy roles yet for backward compatibility
512
+
513
+ # Migrate auth_enabled flag
514
+ if "auth_enabled" in migrated_config and "auth" not in security_config:
515
+ security_config["auth"] = {"enabled": migrated_config["auth_enabled"]}
516
+
517
+ # Migrate rate_limit_enabled flag
518
+ if "rate_limit_enabled" in migrated_config and "rate_limit" not in security_config:
519
+ security_config["rate_limit"] = {"enabled": migrated_config["rate_limit_enabled"]}
520
+
521
+ logger.info("Legacy configuration migrated to new format")
522
+ return migrated_config
523
+
524
+ def get_default_config(self) -> Dict[str, Any]:
525
+ """
526
+ Get default configuration.
527
+
528
+ Returns:
529
+ Default configuration dictionary
530
+ """
531
+ return {
532
+ "security": {
533
+ "enabled": True,
534
+ "auth": {
535
+ "enabled": True,
536
+ "methods": ["api_key"],
537
+ "api_keys": {},
538
+ "jwt_secret": "",
539
+ "jwt_algorithm": "HS256",
540
+ "jwt_expiration": 3600
541
+ },
542
+ "ssl": {
543
+ "enabled": False,
544
+ "cert_file": None,
545
+ "key_file": None,
546
+ "ca_cert": None,
547
+ "min_tls_version": "TLSv1.2",
548
+ "verify_client": False,
549
+ "client_cert_required": False,
550
+ "cipher_suites": []
551
+ },
552
+ "permissions": {
553
+ "enabled": True,
554
+ "roles_file": "roles.json",
555
+ "default_role": "user",
556
+ "deny_by_default": True,
557
+ "role_mappings": {}
558
+ },
559
+ "rate_limit": {
560
+ "enabled": True,
561
+ "requests_per_minute": 60,
562
+ "requests_per_hour": 1000,
563
+ "requests_per_day": 10000,
564
+ "burst_limit": 10,
565
+ "by_ip": True,
566
+ "by_user": True,
567
+ "by_endpoint": False,
568
+ "exempt_roles": [],
569
+ "exempt_endpoints": []
570
+ },
571
+ "public_paths": [
572
+ "/health",
573
+ "/docs",
574
+ "/redoc",
575
+ "/openapi.json",
576
+ "/favicon.ico"
577
+ ]
578
+ }
579
+ }