mcp-proxy-adapter 2.0.1__py3-none-any.whl → 6.9.50__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.

Potentially problematic release.


This version of mcp-proxy-adapter might be problematic. Click here for more details.

Files changed (269) hide show
  1. mcp_proxy_adapter/__init__.py +47 -0
  2. mcp_proxy_adapter/__main__.py +13 -0
  3. mcp_proxy_adapter/api/__init__.py +0 -0
  4. mcp_proxy_adapter/api/app.py +66 -0
  5. mcp_proxy_adapter/api/core/__init__.py +18 -0
  6. mcp_proxy_adapter/api/core/app_factory.py +400 -0
  7. mcp_proxy_adapter/api/core/lifespan_manager.py +55 -0
  8. mcp_proxy_adapter/api/core/registration_context.py +356 -0
  9. mcp_proxy_adapter/api/core/registration_manager.py +307 -0
  10. mcp_proxy_adapter/api/core/registration_tasks.py +84 -0
  11. mcp_proxy_adapter/api/core/ssl_context_factory.py +88 -0
  12. mcp_proxy_adapter/api/handlers.py +181 -0
  13. mcp_proxy_adapter/api/middleware/__init__.py +21 -0
  14. mcp_proxy_adapter/api/middleware/base.py +54 -0
  15. mcp_proxy_adapter/api/middleware/command_permission_middleware.py +73 -0
  16. mcp_proxy_adapter/api/middleware/error_handling.py +76 -0
  17. mcp_proxy_adapter/api/middleware/factory.py +147 -0
  18. mcp_proxy_adapter/api/middleware/logging.py +31 -0
  19. mcp_proxy_adapter/api/middleware/performance.py +51 -0
  20. mcp_proxy_adapter/api/middleware/protocol_middleware.py +140 -0
  21. mcp_proxy_adapter/api/middleware/transport_middleware.py +87 -0
  22. mcp_proxy_adapter/api/middleware/unified_security.py +223 -0
  23. mcp_proxy_adapter/api/middleware/user_info_middleware.py +132 -0
  24. mcp_proxy_adapter/api/openapi/__init__.py +21 -0
  25. mcp_proxy_adapter/api/openapi/command_integration.py +105 -0
  26. mcp_proxy_adapter/api/openapi/openapi_generator.py +40 -0
  27. mcp_proxy_adapter/api/openapi/openapi_registry.py +62 -0
  28. mcp_proxy_adapter/api/openapi/schema_loader.py +116 -0
  29. mcp_proxy_adapter/api/schemas.py +270 -0
  30. mcp_proxy_adapter/api/tool_integration.py +131 -0
  31. mcp_proxy_adapter/api/tools.py +163 -0
  32. mcp_proxy_adapter/cli/__init__.py +12 -0
  33. mcp_proxy_adapter/cli/commands/__init__.py +15 -0
  34. mcp_proxy_adapter/cli/commands/client.py +100 -0
  35. mcp_proxy_adapter/cli/commands/config_generate.py +105 -0
  36. mcp_proxy_adapter/cli/commands/config_validate.py +94 -0
  37. mcp_proxy_adapter/cli/commands/generate.py +259 -0
  38. mcp_proxy_adapter/cli/commands/server.py +174 -0
  39. mcp_proxy_adapter/cli/commands/sets.py +132 -0
  40. mcp_proxy_adapter/cli/commands/testconfig.py +177 -0
  41. mcp_proxy_adapter/cli/examples/__init__.py +8 -0
  42. mcp_proxy_adapter/cli/examples/http_basic.py +82 -0
  43. mcp_proxy_adapter/cli/examples/https_token.py +96 -0
  44. mcp_proxy_adapter/cli/examples/mtls_roles.py +103 -0
  45. mcp_proxy_adapter/cli/main.py +63 -0
  46. mcp_proxy_adapter/cli/parser.py +338 -0
  47. mcp_proxy_adapter/cli/validators.py +231 -0
  48. mcp_proxy_adapter/client/jsonrpc_client/__init__.py +9 -0
  49. mcp_proxy_adapter/client/jsonrpc_client/client.py +42 -0
  50. mcp_proxy_adapter/client/jsonrpc_client/command_api.py +45 -0
  51. mcp_proxy_adapter/client/jsonrpc_client/proxy_api.py +224 -0
  52. mcp_proxy_adapter/client/jsonrpc_client/queue_api.py +60 -0
  53. mcp_proxy_adapter/client/jsonrpc_client/transport.py +108 -0
  54. mcp_proxy_adapter/client/proxy.py +123 -0
  55. mcp_proxy_adapter/commands/__init__.py +66 -0
  56. mcp_proxy_adapter/commands/auth_validation_command.py +69 -0
  57. mcp_proxy_adapter/commands/base.py +389 -0
  58. mcp_proxy_adapter/commands/builtin_commands.py +30 -0
  59. mcp_proxy_adapter/commands/catalog/__init__.py +20 -0
  60. mcp_proxy_adapter/commands/catalog/catalog_loader.py +34 -0
  61. mcp_proxy_adapter/commands/catalog/catalog_manager.py +122 -0
  62. mcp_proxy_adapter/commands/catalog/catalog_syncer.py +149 -0
  63. mcp_proxy_adapter/commands/catalog/command_catalog.py +43 -0
  64. mcp_proxy_adapter/commands/catalog/dependency_manager.py +37 -0
  65. mcp_proxy_adapter/commands/catalog_manager.py +97 -0
  66. mcp_proxy_adapter/commands/cert_monitor_command.py +552 -0
  67. mcp_proxy_adapter/commands/certificate_management_command.py +562 -0
  68. mcp_proxy_adapter/commands/command_registry.py +298 -0
  69. mcp_proxy_adapter/commands/config_command.py +102 -0
  70. mcp_proxy_adapter/commands/dependency_container.py +40 -0
  71. mcp_proxy_adapter/commands/dependency_manager.py +143 -0
  72. mcp_proxy_adapter/commands/echo_command.py +48 -0
  73. mcp_proxy_adapter/commands/health_command.py +142 -0
  74. mcp_proxy_adapter/commands/help_command.py +175 -0
  75. mcp_proxy_adapter/commands/hooks.py +172 -0
  76. mcp_proxy_adapter/commands/key_management_command.py +484 -0
  77. mcp_proxy_adapter/commands/load_command.py +123 -0
  78. mcp_proxy_adapter/commands/plugins_command.py +246 -0
  79. mcp_proxy_adapter/commands/protocol_management_command.py +216 -0
  80. mcp_proxy_adapter/commands/proxy_registration_command.py +319 -0
  81. mcp_proxy_adapter/commands/queue_commands.py +750 -0
  82. mcp_proxy_adapter/commands/registration_status_command.py +76 -0
  83. mcp_proxy_adapter/commands/registry/__init__.py +18 -0
  84. mcp_proxy_adapter/commands/registry/command_info.py +103 -0
  85. mcp_proxy_adapter/commands/registry/command_loader.py +207 -0
  86. mcp_proxy_adapter/commands/registry/command_manager.py +119 -0
  87. mcp_proxy_adapter/commands/registry/command_registry.py +217 -0
  88. mcp_proxy_adapter/commands/reload_command.py +136 -0
  89. mcp_proxy_adapter/commands/result.py +157 -0
  90. mcp_proxy_adapter/commands/role_test_command.py +99 -0
  91. mcp_proxy_adapter/commands/roles_management_command.py +502 -0
  92. mcp_proxy_adapter/commands/security_command.py +472 -0
  93. mcp_proxy_adapter/commands/settings_command.py +113 -0
  94. mcp_proxy_adapter/commands/ssl_setup_command.py +306 -0
  95. mcp_proxy_adapter/commands/token_management_command.py +500 -0
  96. mcp_proxy_adapter/commands/transport_management_command.py +129 -0
  97. mcp_proxy_adapter/commands/unload_command.py +92 -0
  98. mcp_proxy_adapter/config.py +32 -0
  99. mcp_proxy_adapter/core/__init__.py +8 -0
  100. mcp_proxy_adapter/core/app_factory.py +560 -0
  101. mcp_proxy_adapter/core/app_runner.py +318 -0
  102. mcp_proxy_adapter/core/auth_validator.py +508 -0
  103. mcp_proxy_adapter/core/certificate/__init__.py +20 -0
  104. mcp_proxy_adapter/core/certificate/certificate_creator.py +372 -0
  105. mcp_proxy_adapter/core/certificate/certificate_extractor.py +185 -0
  106. mcp_proxy_adapter/core/certificate/certificate_utils.py +249 -0
  107. mcp_proxy_adapter/core/certificate/certificate_validator.py +481 -0
  108. mcp_proxy_adapter/core/certificate/ssl_context_manager.py +65 -0
  109. mcp_proxy_adapter/core/certificate_utils.py +249 -0
  110. mcp_proxy_adapter/core/client.py +608 -0
  111. mcp_proxy_adapter/core/client_manager.py +271 -0
  112. mcp_proxy_adapter/core/client_security.py +411 -0
  113. mcp_proxy_adapter/core/config/__init__.py +18 -0
  114. mcp_proxy_adapter/core/config/config.py +237 -0
  115. mcp_proxy_adapter/core/config/config_factory.py +22 -0
  116. mcp_proxy_adapter/core/config/config_loader.py +66 -0
  117. mcp_proxy_adapter/core/config/feature_manager.py +31 -0
  118. mcp_proxy_adapter/core/config/simple_config.py +204 -0
  119. mcp_proxy_adapter/core/config/simple_config_generator.py +131 -0
  120. mcp_proxy_adapter/core/config/simple_config_validator.py +476 -0
  121. mcp_proxy_adapter/core/config_converter.py +252 -0
  122. mcp_proxy_adapter/core/config_validator.py +211 -0
  123. mcp_proxy_adapter/core/crl_utils.py +362 -0
  124. mcp_proxy_adapter/core/errors.py +276 -0
  125. mcp_proxy_adapter/core/job_manager.py +54 -0
  126. mcp_proxy_adapter/core/logging.py +250 -0
  127. mcp_proxy_adapter/core/mtls_asgi.py +140 -0
  128. mcp_proxy_adapter/core/mtls_asgi_app.py +187 -0
  129. mcp_proxy_adapter/core/mtls_proxy.py +229 -0
  130. mcp_proxy_adapter/core/mtls_server.py +154 -0
  131. mcp_proxy_adapter/core/protocol_manager.py +232 -0
  132. mcp_proxy_adapter/core/proxy/__init__.py +19 -0
  133. mcp_proxy_adapter/core/proxy/auth_manager.py +26 -0
  134. mcp_proxy_adapter/core/proxy/proxy_registration_manager.py +160 -0
  135. mcp_proxy_adapter/core/proxy/registration_client.py +186 -0
  136. mcp_proxy_adapter/core/proxy/ssl_manager.py +101 -0
  137. mcp_proxy_adapter/core/proxy_client.py +184 -0
  138. mcp_proxy_adapter/core/proxy_registration.py +80 -0
  139. mcp_proxy_adapter/core/role_utils.py +103 -0
  140. mcp_proxy_adapter/core/security_adapter.py +343 -0
  141. mcp_proxy_adapter/core/security_factory.py +96 -0
  142. mcp_proxy_adapter/core/security_integration.py +342 -0
  143. mcp_proxy_adapter/core/server_adapter.py +251 -0
  144. mcp_proxy_adapter/core/server_engine.py +217 -0
  145. mcp_proxy_adapter/core/settings.py +260 -0
  146. mcp_proxy_adapter/core/signal_handler.py +107 -0
  147. mcp_proxy_adapter/core/ssl_utils.py +161 -0
  148. mcp_proxy_adapter/core/transport_manager.py +153 -0
  149. mcp_proxy_adapter/core/unified_config_adapter.py +471 -0
  150. mcp_proxy_adapter/core/utils.py +101 -0
  151. mcp_proxy_adapter/core/validation/__init__.py +21 -0
  152. mcp_proxy_adapter/core/validation/config_validator.py +219 -0
  153. mcp_proxy_adapter/core/validation/file_validator.py +131 -0
  154. mcp_proxy_adapter/core/validation/protocol_validator.py +205 -0
  155. mcp_proxy_adapter/core/validation/security_validator.py +140 -0
  156. mcp_proxy_adapter/core/validation/validation_result.py +27 -0
  157. mcp_proxy_adapter/custom_openapi.py +58 -0
  158. mcp_proxy_adapter/examples/__init__.py +16 -0
  159. mcp_proxy_adapter/examples/basic_framework/__init__.py +9 -0
  160. mcp_proxy_adapter/examples/basic_framework/commands/__init__.py +4 -0
  161. mcp_proxy_adapter/examples/basic_framework/hooks/__init__.py +4 -0
  162. mcp_proxy_adapter/examples/basic_framework/main.py +52 -0
  163. mcp_proxy_adapter/examples/bugfix_certificate_config.py +261 -0
  164. mcp_proxy_adapter/examples/cert_manager_bugfix.py +203 -0
  165. mcp_proxy_adapter/examples/check_config.py +413 -0
  166. mcp_proxy_adapter/examples/client_usage_example.py +164 -0
  167. mcp_proxy_adapter/examples/commands/__init__.py +5 -0
  168. mcp_proxy_adapter/examples/config_builder.py +234 -0
  169. mcp_proxy_adapter/examples/config_cli.py +282 -0
  170. mcp_proxy_adapter/examples/create_test_configs.py +174 -0
  171. mcp_proxy_adapter/examples/debug_request_state.py +130 -0
  172. mcp_proxy_adapter/examples/debug_role_chain.py +191 -0
  173. mcp_proxy_adapter/examples/demo_client.py +287 -0
  174. mcp_proxy_adapter/examples/full_application/__init__.py +12 -0
  175. mcp_proxy_adapter/examples/full_application/commands/__init__.py +8 -0
  176. mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +45 -0
  177. mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +52 -0
  178. mcp_proxy_adapter/examples/full_application/commands/echo_command.py +32 -0
  179. mcp_proxy_adapter/examples/full_application/commands/help_command.py +54 -0
  180. mcp_proxy_adapter/examples/full_application/commands/list_command.py +57 -0
  181. mcp_proxy_adapter/examples/full_application/hooks/__init__.py +5 -0
  182. mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +29 -0
  183. mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +27 -0
  184. mcp_proxy_adapter/examples/full_application/main.py +311 -0
  185. mcp_proxy_adapter/examples/full_application/proxy_endpoints.py +161 -0
  186. mcp_proxy_adapter/examples/full_application/run_mtls.py +252 -0
  187. mcp_proxy_adapter/examples/full_application/run_simple.py +152 -0
  188. mcp_proxy_adapter/examples/full_application/test_minimal_server.py +45 -0
  189. mcp_proxy_adapter/examples/full_application/test_server.py +163 -0
  190. mcp_proxy_adapter/examples/full_application/test_simple_server.py +62 -0
  191. mcp_proxy_adapter/examples/generate_config.py +502 -0
  192. mcp_proxy_adapter/examples/proxy_registration_example.py +335 -0
  193. mcp_proxy_adapter/examples/queue_demo_simple.py +632 -0
  194. mcp_proxy_adapter/examples/queue_integration_example.py +578 -0
  195. mcp_proxy_adapter/examples/queue_server_demo.py +82 -0
  196. mcp_proxy_adapter/examples/queue_server_example.py +85 -0
  197. mcp_proxy_adapter/examples/queue_server_simple.py +173 -0
  198. mcp_proxy_adapter/examples/required_certificates.py +208 -0
  199. mcp_proxy_adapter/examples/run_example.py +77 -0
  200. mcp_proxy_adapter/examples/run_full_test_suite.py +619 -0
  201. mcp_proxy_adapter/examples/run_proxy_server.py +153 -0
  202. mcp_proxy_adapter/examples/run_security_tests_fixed.py +435 -0
  203. mcp_proxy_adapter/examples/security_test/__init__.py +18 -0
  204. mcp_proxy_adapter/examples/security_test/auth_manager.py +14 -0
  205. mcp_proxy_adapter/examples/security_test/ssl_context_manager.py +28 -0
  206. mcp_proxy_adapter/examples/security_test/test_client.py +159 -0
  207. mcp_proxy_adapter/examples/security_test/test_result.py +22 -0
  208. mcp_proxy_adapter/examples/security_test_client.py +72 -0
  209. mcp_proxy_adapter/examples/setup/__init__.py +24 -0
  210. mcp_proxy_adapter/examples/setup/certificate_manager.py +215 -0
  211. mcp_proxy_adapter/examples/setup/config_generator.py +12 -0
  212. mcp_proxy_adapter/examples/setup/config_validator.py +118 -0
  213. mcp_proxy_adapter/examples/setup/environment_setup.py +62 -0
  214. mcp_proxy_adapter/examples/setup/test_files_generator.py +10 -0
  215. mcp_proxy_adapter/examples/setup/test_runner.py +89 -0
  216. mcp_proxy_adapter/examples/setup_test_environment.py +235 -0
  217. mcp_proxy_adapter/examples/simple_protocol_test.py +125 -0
  218. mcp_proxy_adapter/examples/test_chk_hostname_automated.py +211 -0
  219. mcp_proxy_adapter/examples/test_config.py +205 -0
  220. mcp_proxy_adapter/examples/test_config_builder.py +110 -0
  221. mcp_proxy_adapter/examples/test_examples.py +308 -0
  222. mcp_proxy_adapter/examples/test_framework_complete.py +267 -0
  223. mcp_proxy_adapter/examples/test_mcp_server.py +187 -0
  224. mcp_proxy_adapter/examples/test_protocol_examples.py +337 -0
  225. mcp_proxy_adapter/examples/universal_client.py +674 -0
  226. mcp_proxy_adapter/examples/update_config_certificates.py +135 -0
  227. mcp_proxy_adapter/examples/validate_generator_compatibility.py +385 -0
  228. mcp_proxy_adapter/examples/validate_generator_compatibility_simple.py +61 -0
  229. mcp_proxy_adapter/integrations/__init__.py +25 -0
  230. mcp_proxy_adapter/integrations/queuemgr_integration.py +462 -0
  231. mcp_proxy_adapter/main.py +311 -0
  232. mcp_proxy_adapter/openapi.py +375 -0
  233. mcp_proxy_adapter/schemas/base_schema.json +114 -0
  234. mcp_proxy_adapter/schemas/openapi_schema.json +314 -0
  235. mcp_proxy_adapter/schemas/roles.json +37 -0
  236. mcp_proxy_adapter/schemas/roles_schema.json +162 -0
  237. mcp_proxy_adapter/version.py +5 -0
  238. mcp_proxy_adapter-6.9.50.dist-info/METADATA +1088 -0
  239. mcp_proxy_adapter-6.9.50.dist-info/RECORD +242 -0
  240. {mcp_proxy_adapter-2.0.1.dist-info → mcp_proxy_adapter-6.9.50.dist-info}/WHEEL +1 -1
  241. mcp_proxy_adapter-6.9.50.dist-info/entry_points.txt +14 -0
  242. mcp_proxy_adapter-6.9.50.dist-info/top_level.txt +1 -0
  243. adapters/__init__.py +0 -16
  244. analyzers/__init__.py +0 -14
  245. analyzers/docstring_analyzer.py +0 -199
  246. analyzers/type_analyzer.py +0 -151
  247. cli/__init__.py +0 -12
  248. cli/__main__.py +0 -79
  249. cli/command_runner.py +0 -233
  250. dispatchers/__init__.py +0 -14
  251. dispatchers/base_dispatcher.py +0 -85
  252. dispatchers/json_rpc_dispatcher.py +0 -198
  253. generators/__init__.py +0 -14
  254. generators/endpoint_generator.py +0 -172
  255. generators/openapi_generator.py +0 -254
  256. generators/rest_api_generator.py +0 -207
  257. mcp_proxy_adapter-2.0.1.dist-info/METADATA +0 -272
  258. mcp_proxy_adapter-2.0.1.dist-info/RECORD +0 -28
  259. mcp_proxy_adapter-2.0.1.dist-info/licenses/LICENSE +0 -21
  260. mcp_proxy_adapter-2.0.1.dist-info/top_level.txt +0 -7
  261. openapi_schema/__init__.py +0 -38
  262. openapi_schema/command_registry.py +0 -312
  263. openapi_schema/rest_schema.py +0 -510
  264. openapi_schema/rpc_generator.py +0 -307
  265. openapi_schema/rpc_schema.py +0 -416
  266. validators/__init__.py +0 -14
  267. validators/base_validator.py +0 -23
  268. validators/docstring_validator.py +0 -75
  269. validators/metadata_validator.py +0 -76
@@ -0,0 +1,471 @@
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 pathlib import Path
14
+ from dataclasses import dataclass
15
+ from typing import List, Dict, Any
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,
22
+ SSLConfig,
23
+ PermissionConfig,
24
+ RateLimitConfig,
25
+ )
26
+
27
+ SECURITY_FRAMEWORK_AVAILABLE = True
28
+ except ImportError as e:
29
+ # NO FALLBACK! mcp_security_framework is REQUIRED
30
+ raise RuntimeError(
31
+ f"CRITICAL: mcp_security_framework is required but not available: {e}. "
32
+ "Install it with: pip install mcp_security_framework>=1.2.8"
33
+ ) from e
34
+
35
+ from mcp_proxy_adapter.core.logging import get_global_logger
36
+
37
+
38
+ @dataclass
39
+ class ValidationResult:
40
+ """Result of configuration validation."""
41
+
42
+ is_valid: bool
43
+ errors: List[str]
44
+ warnings: List[str]
45
+ details: Dict[str, Any]
46
+
47
+ def __post_init__(self):
48
+ """Initialize with empty lists if None."""
49
+ if self.errors is None:
50
+ self.errors = []
51
+ if self.warnings is None:
52
+ self.warnings = []
53
+ if self.details is None:
54
+ self.details = {}
55
+
56
+
57
+ class UnifiedConfigAdapter:
58
+ """
59
+ Unified adapter for converting mcp_proxy_adapter configuration to SecurityConfig.
60
+
61
+ This adapter handles:
62
+ - Legacy configuration format compatibility
63
+ - Configuration validation
64
+ - Conversion to mcp_security_framework format
65
+ - Default value management
66
+ """
67
+
68
+ def __init__(self):
69
+ """Initialize the unified configuration adapter."""
70
+ self.validation_errors = []
71
+ self.validation_warnings = []
72
+
73
+
74
+ def validate_configuration(self, config: Dict[str, Any]) -> ValidationResult:
75
+ """
76
+ Validate configuration before conversion.
77
+
78
+ Args:
79
+ config: Configuration dictionary to validate
80
+
81
+ Returns:
82
+ ValidationResult with validation details
83
+ """
84
+ self.validation_errors = []
85
+ self.validation_warnings = []
86
+
87
+ # Debug: Check SSL config at start of validation
88
+ if "security" in config:
89
+ ssl_config = config["security"].get("ssl", {})
90
+ print(
91
+ f"🔍 Debug: SSL config at start of validation: enabled={ssl_config.get('enabled', False)}"
92
+ )
93
+
94
+ # Debug: Check if root ssl section exists
95
+ if "ssl" in config:
96
+ print(
97
+ f"🔍 Debug: Root SSL section found: enabled={config['ssl'].get('enabled', False)}"
98
+ )
99
+
100
+ # Check if config is a dictionary
101
+ if not isinstance(config, dict):
102
+ return ValidationResult(
103
+ is_valid=False,
104
+ errors=["Configuration must be a dictionary"],
105
+ warnings=[],
106
+ details={},
107
+ )
108
+
109
+ # Validate security section
110
+ self._validate_security_section(config)
111
+
112
+ # Validate legacy sections for compatibility
113
+ self._validate_legacy_sections(config)
114
+
115
+ # Check for conflicts
116
+ self._check_configuration_conflicts(config)
117
+
118
+ # Validate individual sections
119
+ self._validate_auth_section(config)
120
+ self._validate_ssl_section(config)
121
+ self._validate_permissions_section(config)
122
+ self._validate_rate_limit_section(config)
123
+
124
+ return ValidationResult(
125
+ is_valid=len(self.validation_errors) == 0,
126
+ errors=self.validation_errors.copy(),
127
+ warnings=self.validation_warnings.copy(),
128
+ details={
129
+ "has_security_section": "security" in config,
130
+ "has_legacy_sections": any(
131
+ key in config for key in ["ssl", "roles", "auth_enabled"]
132
+ ),
133
+ "total_errors": len(self.validation_errors),
134
+ "total_warnings": len(self.validation_warnings),
135
+ },
136
+ )
137
+
138
+ def _validate_security_section(self, config: Dict[str, Any]):
139
+ """Validate security section."""
140
+ security_config = config.get("security", {})
141
+
142
+ if not isinstance(security_config, dict):
143
+ self.validation_errors.append("Security section must be a dictionary")
144
+ return
145
+
146
+ # Check for unknown keys in security section
147
+ known_keys = {
148
+ "enabled",
149
+ "auth",
150
+ "ssl",
151
+ "permissions",
152
+ "rate_limit",
153
+ "public_paths",
154
+ }
155
+ unknown_keys = set(security_config.keys()) - known_keys
156
+
157
+ if unknown_keys:
158
+ self.validation_warnings.append(
159
+ f"Unknown keys in security section: {unknown_keys}"
160
+ )
161
+
162
+ def _validate_legacy_sections(self, config: Dict[str, Any]):
163
+ """Validate legacy configuration sections."""
164
+ legacy_sections = ["ssl", "roles", "auth_enabled", "rate_limit_enabled"]
165
+
166
+ for section in legacy_sections:
167
+ if section in config:
168
+ self.validation_warnings.append(
169
+ f"Legacy section '{section}' found, consider migrating to security section"
170
+ )
171
+
172
+ def _check_configuration_conflicts(self, config: Dict[str, Any]):
173
+ """Check for configuration conflicts."""
174
+ security_config = config.get("security", {})
175
+
176
+ # Check for SSL configuration conflicts
177
+ if "ssl" in config and "ssl" in security_config:
178
+ self.validation_warnings.append(
179
+ "SSL configuration found in both root and security sections"
180
+ )
181
+
182
+ # Check for auth configuration conflicts
183
+ if "auth_enabled" in config and "auth" in security_config:
184
+ self.validation_warnings.append(
185
+ "Auth configuration found in both root and security sections"
186
+ )
187
+
188
+ def _validate_auth_section(self, config: Dict[str, Any]):
189
+ """Validate authentication section."""
190
+ auth_config = self._get_auth_config(config)
191
+
192
+ if not isinstance(auth_config, dict):
193
+ self.validation_errors.append("Auth configuration must be a dictionary")
194
+ return
195
+
196
+ # Validate auth methods
197
+ methods = auth_config.get("methods", [])
198
+ if not isinstance(methods, list):
199
+ self.validation_errors.append("Auth methods must be a list")
200
+ else:
201
+ valid_methods = {"api_key", "jwt", "certificate"}
202
+ invalid_methods = set(methods) - valid_methods
203
+ if invalid_methods:
204
+ self.validation_errors.append(
205
+ f"Invalid auth methods: {invalid_methods}"
206
+ )
207
+
208
+ # Validate API keys
209
+ api_keys = auth_config.get("api_keys", {})
210
+ if not isinstance(api_keys, dict):
211
+ self.validation_errors.append("API keys must be a dictionary")
212
+
213
+ # Validate JWT configuration
214
+ if "jwt" in methods:
215
+ jwt_secret = auth_config.get("jwt_secret", "")
216
+ if not jwt_secret:
217
+ self.validation_warnings.append("JWT secret is empty or not set")
218
+
219
+ def _validate_ssl_section(self, config: Dict[str, Any]):
220
+ """Validate SSL section."""
221
+ ssl_config = self._get_ssl_config(config)
222
+
223
+ if not isinstance(ssl_config, dict):
224
+ self.validation_errors.append("SSL configuration must be a dictionary")
225
+ return
226
+
227
+ # Validate certificate files
228
+ if ssl_config.get("enabled", False):
229
+ cert_file = ssl_config.get("cert_file")
230
+ key_file = ssl_config.get("key_file")
231
+
232
+ print(f"🔍 Debug: _validate_ssl_section: cert_file={cert_file}")
233
+ print(f"🔍 Debug: _validate_ssl_section: key_file={key_file}")
234
+ print(
235
+ f"🔍 Debug: _validate_ssl_section: cert_file exists={Path(cert_file).exists() if cert_file else 'None'}"
236
+ )
237
+ print(
238
+ f"🔍 Debug: _validate_ssl_section: key_file exists={Path(key_file).exists() if key_file else 'None'}"
239
+ )
240
+
241
+ if cert_file and not Path(cert_file).exists():
242
+ self.validation_warnings.append(
243
+ f"SSL certificate file not found: {cert_file}"
244
+ )
245
+
246
+ if key_file and not Path(key_file).exists():
247
+ self.validation_warnings.append(f"SSL key file not found: {key_file}")
248
+
249
+ def _validate_permissions_section(self, config: Dict[str, Any]):
250
+ """Validate permissions section."""
251
+ permissions_config = self._get_permissions_config(config)
252
+
253
+ if not isinstance(permissions_config, dict):
254
+ self.validation_errors.append(
255
+ "Permissions configuration must be a dictionary"
256
+ )
257
+ return
258
+
259
+ # Validate roles file
260
+ if permissions_config.get("enabled", False):
261
+ roles_file = permissions_config.get("roles_file")
262
+ if roles_file and not Path(roles_file).exists():
263
+ self.validation_warnings.append(f"Roles file not found: {roles_file}")
264
+
265
+ def _validate_rate_limit_section(self, config: Dict[str, Any]):
266
+ """Validate rate limit section."""
267
+ rate_limit_config = self._get_rate_limit_config(config)
268
+
269
+ if not isinstance(rate_limit_config, dict):
270
+ self.validation_errors.append(
271
+ "Rate limit configuration must be a dictionary"
272
+ )
273
+ return
274
+
275
+ # Validate rate limit values only if enabled
276
+ if rate_limit_config.get("enabled", False):
277
+ requests_per_minute = rate_limit_config.get(
278
+ "requests_per_minute",
279
+ rate_limit_config.get("default_requests_per_minute", 0),
280
+ )
281
+ if requests_per_minute <= 0:
282
+ self.validation_errors.append(
283
+ "requests_per_minute must be greater than 0 when rate limiting is enabled"
284
+ )
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(
381
+ f"🔍 Debug: _get_ssl_config: security.ssl key_file={ssl_config.get('key_file')}"
382
+ )
383
+
384
+ # Merge with legacy SSL config, but prioritize security.ssl over legacy ssl
385
+ legacy_ssl = config.get("ssl", {})
386
+ if legacy_ssl:
387
+ print(
388
+ f"🔍 Debug: _get_ssl_config: legacy ssl key_file={legacy_ssl.get('key_file')}"
389
+ )
390
+ # Only merge legacy config if security.ssl is not enabled or missing
391
+ if not ssl_config.get("enabled", False):
392
+ ssl_config.update(legacy_ssl)
393
+ else:
394
+ # If security.ssl is enabled, only merge non-conflicting fields
395
+ for key, value in legacy_ssl.items():
396
+ if key not in ssl_config or ssl_config[key] is None:
397
+ ssl_config[key] = value
398
+
399
+ print(f"🔍 Debug: _get_ssl_config: final key_file={ssl_config.get('key_file')}")
400
+ return ssl_config
401
+
402
+ def _get_permissions_config(self, config: Dict[str, Any]) -> Dict[str, Any]:
403
+ """Get permissions configuration from config."""
404
+ security_config = config.get("security", {})
405
+
406
+ # Ensure security_config is a dictionary
407
+ if not isinstance(security_config, dict):
408
+ return {}
409
+
410
+ permissions_config = security_config.get("permissions", {})
411
+
412
+ # Handle security.roles - convert to role_mappings format
413
+ security_roles = security_config.get("roles", {})
414
+ if security_roles:
415
+ # Convert roles format to role_mappings
416
+ # If roles is a dict where values are lists (token -> roles mapping)
417
+ # or dict where values are objects with permissions field
418
+ role_mappings = {}
419
+ for role_name, role_value in security_roles.items():
420
+ if isinstance(role_value, list):
421
+ # Format: "role_name": ["permission1", "permission2"]
422
+ role_mappings[role_name] = role_value
423
+ elif isinstance(role_value, dict):
424
+ # Format: "role_name": {"permissions": [...], "description": "..."}
425
+ # Extract permissions field
426
+ if "permissions" in role_value:
427
+ role_mappings[role_name] = role_value["permissions"]
428
+ else:
429
+ # If no permissions field, use empty list
430
+ role_mappings[role_name] = []
431
+ else:
432
+ # Single string permission
433
+ role_mappings[role_name] = [role_value] if isinstance(role_value, str) else []
434
+
435
+ # Set role_mappings in permissions_config
436
+ if role_mappings:
437
+ permissions_config["role_mappings"] = role_mappings
438
+
439
+ # Merge with legacy roles config (from root level)
440
+ legacy_roles = config.get("roles", {})
441
+ if legacy_roles and isinstance(legacy_roles, dict):
442
+ # Convert legacy roles format if needed
443
+ if "role_mappings" not in permissions_config:
444
+ permissions_config["role_mappings"] = {}
445
+ for role_name, role_value in legacy_roles.items():
446
+ if isinstance(role_value, list):
447
+ permissions_config["role_mappings"][role_name] = role_value
448
+ elif isinstance(role_value, dict) and "permissions" in role_value:
449
+ permissions_config["role_mappings"][role_name] = role_value["permissions"]
450
+
451
+ return permissions_config
452
+
453
+ def _get_rate_limit_config(self, config: Dict[str, Any]) -> Dict[str, Any]:
454
+ """Get rate limit configuration from config."""
455
+ security_config = config.get("security", {})
456
+
457
+ # Ensure security_config is a dictionary
458
+ if not isinstance(security_config, dict):
459
+ return {}
460
+
461
+ rate_limit_config = security_config.get("rate_limit", {})
462
+
463
+ # Handle legacy rate_limit_enabled flag
464
+ if config.get("rate_limit_enabled") is not None:
465
+ rate_limit_config["enabled"] = config["rate_limit_enabled"]
466
+
467
+ return rate_limit_config
468
+
469
+
470
+
471
+
@@ -0,0 +1,101 @@
1
+ """
2
+ Module with utility functions for the microservice.
3
+ """
4
+
5
+ import hashlib
6
+ import json
7
+ import os
8
+ import socket
9
+ import sys
10
+ import time
11
+ import uuid
12
+ from datetime import datetime, timezone
13
+ from typing import Optional, Dict, Any, List
14
+
15
+ from mcp_proxy_adapter.core.logging import get_global_logger
16
+
17
+
18
+
19
+
20
+
21
+
22
+
23
+
24
+
25
+
26
+
27
+
28
+
29
+
30
+
31
+
32
+
33
+
34
+ def check_port_availability(host: str, port: int, timeout: float = 1.0) -> bool:
35
+ """
36
+ Checks if a port is available for binding.
37
+
38
+ Args:
39
+ host: Host address to check
40
+ port: Port number to check
41
+ timeout: Connection timeout in seconds
42
+
43
+ Returns:
44
+ True if port is available, False if port is in use
45
+ """
46
+ try:
47
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
48
+ sock.settimeout(timeout)
49
+ result = sock.connect_ex((host, port))
50
+ return result != 0 # True if connection failed (port is free)
51
+ except Exception as e:
52
+ get_global_logger().warning(f"Error checking port {port} on {host}: {e}")
53
+ return True # Assume port is available if check fails
54
+
55
+
56
+ def find_available_port(host: str, start_port: int, max_attempts: int = 100) -> Optional[int]:
57
+ """
58
+ Finds an available port starting from the specified port.
59
+
60
+ Args:
61
+ host: Host address to check
62
+ start_port: Starting port number
63
+ max_attempts: Maximum number of ports to check
64
+
65
+ Returns:
66
+ Available port number or None if no port found
67
+ """
68
+ for port in range(start_port, start_port + max_attempts):
69
+ if check_port_availability(host, port):
70
+ return port
71
+ return None
72
+
73
+
74
+ def get_port_usage_info(port: int) -> str:
75
+ """
76
+ Gets information about what process is using a port.
77
+
78
+ Args:
79
+ port: Port number to check
80
+
81
+ Returns:
82
+ String with port usage information
83
+ """
84
+ try:
85
+ import subprocess
86
+ result = subprocess.run(
87
+ ["lsof", "-i", f":{port}"],
88
+ capture_output=True,
89
+ text=True,
90
+ timeout=5
91
+ )
92
+ if result.returncode == 0 and result.stdout.strip():
93
+ return f"Port {port} is used by:\n{result.stdout.strip()}"
94
+ else:
95
+ return f"Port {port} appears to be in use but process info unavailable"
96
+ except Exception as e:
97
+ return f"Port {port} is in use (unable to get process info: {e})"
98
+
99
+
100
+
101
+
@@ -0,0 +1,21 @@
1
+ """
2
+ Author: Vasiliy Zdanovskiy
3
+ email: vasilyvz@gmail.com
4
+
5
+ Configuration validation package for MCP Proxy Adapter.
6
+ """
7
+
8
+ from .config_validator import ConfigValidator
9
+ from .validation_result import ValidationResult, ValidationLevel
10
+ from .file_validator import FileValidator
11
+ from .security_validator import SecurityValidator
12
+ from .protocol_validator import ProtocolValidator
13
+
14
+ __all__ = [
15
+ "ConfigValidator",
16
+ "ValidationResult",
17
+ "ValidationLevel",
18
+ "FileValidator",
19
+ "SecurityValidator",
20
+ "ProtocolValidator",
21
+ ]