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,160 @@
1
+ """
2
+ Author: Vasiliy Zdanovskiy
3
+ email: vasilyvz@gmail.com
4
+
5
+ Main proxy registration manager for MCP Proxy Adapter.
6
+ """
7
+
8
+ import time
9
+ from typing import Dict, Any
10
+
11
+ from mcp_proxy_adapter.core.logging import get_global_logger
12
+ from mcp_proxy_adapter.core.client_security import create_client_security_manager
13
+ from .registration_client import RegistrationClient
14
+
15
+
16
+ class ProxyRegistrationError(Exception):
17
+ """Exception raised when proxy registration fails."""
18
+
19
+ pass
20
+
21
+
22
+ class ProxyRegistrationManager:
23
+ """
24
+ Manager for proxy registration functionality with security framework integration.
25
+
26
+ Handles automatic registration and unregistration of the server
27
+ with the MCP proxy server using secure authentication methods.
28
+ """
29
+
30
+ def __init__(self, config: Dict[str, Any]):
31
+ """
32
+ Initialize the proxy registration manager.
33
+
34
+ Args:
35
+ config: Application configuration
36
+ """
37
+ self.config = config
38
+ self.logger = get_global_logger()
39
+
40
+ # Get registration configuration
41
+ self.registration_config = config.get("proxy_registration", {})
42
+
43
+ # Initialize client security
44
+ self.client_security = create_client_security_manager(config)
45
+
46
+ # Registration state
47
+ self.proxy_url = self.registration_config.get("proxy_url")
48
+ self.server_url = None
49
+ self.registered = False
50
+ self.registration_time = None
51
+
52
+ # Initialize registration client
53
+ self.registration_client = RegistrationClient(
54
+ self.client_security, self.registration_config, config, self.proxy_url
55
+ )
56
+
57
+ def is_enabled(self) -> bool:
58
+ """
59
+ Check if proxy registration is enabled.
60
+
61
+ Returns:
62
+ True if enabled, False otherwise
63
+ """
64
+ return self.registration_config.get("enabled", False)
65
+
66
+ async def register(self) -> bool:
67
+ """
68
+ Register server with proxy.
69
+
70
+ Returns:
71
+ True if registration successful, False otherwise
72
+ """
73
+ if not self.is_enabled():
74
+ self.logger.info("Proxy registration is disabled")
75
+ return True
76
+
77
+ if not self.server_url:
78
+ self.logger.error("Server URL not set for registration")
79
+ return False
80
+
81
+ if not self.proxy_url:
82
+ self.logger.error("Proxy URL not configured")
83
+ return False
84
+
85
+ try:
86
+ self.logger.info(f"Registering with proxy: {self.proxy_url}")
87
+
88
+ success = await self.registration_client.register(self.server_url)
89
+
90
+ if success:
91
+ self.registered = True
92
+ self.registration_time = time.time()
93
+ self.logger.info("✅ Proxy registration completed successfully")
94
+ else:
95
+ self.logger.error("❌ Proxy registration failed")
96
+
97
+ return success
98
+
99
+ except Exception as e:
100
+ self.logger.error(f"Registration error: {e}")
101
+ return False
102
+
103
+ async def unregister(self) -> bool:
104
+ """
105
+ Unregister server from proxy.
106
+
107
+ Returns:
108
+ True if unregistration successful, False otherwise
109
+ """
110
+ if not self.is_enabled():
111
+ self.logger.info("Proxy registration is disabled")
112
+ return True
113
+
114
+ if not self.registered:
115
+ self.logger.info("Server not registered, skipping unregistration")
116
+ return True
117
+
118
+ try:
119
+ self.logger.info("Unregistering from proxy")
120
+
121
+ success = await self.registration_client.unregister()
122
+
123
+ if success:
124
+ self.registered = False
125
+ self.registration_time = None
126
+ self.logger.info("✅ Proxy unregistration completed successfully")
127
+ else:
128
+ self.logger.warning("⚠️ Proxy unregistration failed")
129
+
130
+ return success
131
+
132
+ except Exception as e:
133
+ self.logger.error(f"Unregistration error: {e}")
134
+ return False
135
+
136
+ def set_server_url(self, server_url: str) -> None:
137
+ """
138
+ Set server URL for registration.
139
+
140
+ Args:
141
+ server_url: Server URL to register
142
+ """
143
+ self.server_url = server_url
144
+ self.logger.info(f"Server URL set: {server_url}")
145
+
146
+ def get_registration_status(self) -> Dict[str, Any]:
147
+ """
148
+ Get current registration status.
149
+
150
+ Returns:
151
+ Dictionary with registration status information
152
+ """
153
+ return {
154
+ "enabled": self.is_enabled(),
155
+ "registered": self.registered,
156
+ "proxy_url": self.proxy_url,
157
+ "server_url": self.server_url,
158
+ "registration_time": self.registration_time,
159
+ "client_security_available": self.client_security is not None,
160
+ }
@@ -0,0 +1,186 @@
1
+ """
2
+ Author: Vasiliy Zdanovskiy
3
+ email: vasilyvz@gmail.com
4
+
5
+ Registration client for proxy registration.
6
+ """
7
+
8
+ from typing import Dict, Any
9
+ import aiohttp
10
+
11
+ from mcp_proxy_adapter.core.logging import get_global_logger
12
+ from .auth_manager import AuthManager
13
+ from .ssl_manager import SSLManager
14
+
15
+
16
+ class RegistrationClient:
17
+ """Client for proxy registration operations."""
18
+
19
+ def __init__(
20
+ self,
21
+ client_security,
22
+ registration_config: Dict[str, Any],
23
+ config: Dict[str, Any],
24
+ proxy_url: str,
25
+ ):
26
+ """
27
+ Initialize registration client.
28
+
29
+ Args:
30
+ client_security: Client security manager instance
31
+ registration_config: Registration configuration
32
+ config: Application configuration
33
+ proxy_url: Proxy server URL
34
+ """
35
+ self.client_security = client_security
36
+ self.registration_config = registration_config
37
+ self.config = config
38
+ self.proxy_url = proxy_url
39
+ self.logger = get_global_logger()
40
+
41
+ # Initialize managers
42
+ self.auth_manager = AuthManager(client_security, registration_config)
43
+ self.ssl_manager = SSLManager(
44
+ client_security, registration_config, config, proxy_url
45
+ )
46
+
47
+ def _prepare_registration_data(self, server_url: str) -> Dict[str, Any]:
48
+ """
49
+ Prepare registration data.
50
+
51
+ Args:
52
+ server_url: Server URL to register
53
+
54
+ Returns:
55
+ Registration data dictionary
56
+ """
57
+ # Proxy expects "name" field, use server_id or server_name
58
+ server_name = (
59
+ self.registration_config.get("server_id")
60
+ or self.registration_config.get("server_name")
61
+ or "mcp_proxy_adapter"
62
+ )
63
+
64
+ return {
65
+ "name": server_name,
66
+ "url": server_url,
67
+ "capabilities": self.registration_config.get("capabilities", ["jsonrpc"]),
68
+ "metadata": {
69
+ "server_id": self.registration_config.get("server_id"),
70
+ "server_name": self.registration_config.get("server_name"),
71
+ "description": self.registration_config.get("description", ""),
72
+ "version": self.registration_config.get("version", "1.0.0"),
73
+ },
74
+ }
75
+
76
+ async def register(self, server_url: str) -> bool:
77
+ """
78
+ Register server with proxy.
79
+
80
+ Args:
81
+ server_url: Server URL to register
82
+
83
+ Returns:
84
+ True if registration successful, False otherwise
85
+ """
86
+ try:
87
+ registration_data = self._prepare_registration_data(server_url)
88
+
89
+ # Get SSL context if needed
90
+ ssl_context = self.ssl_manager.get_ssl_context()
91
+
92
+ # Get headers with authentication if needed
93
+ headers = self.auth_manager.get_headers()
94
+
95
+ # Prepare request configuration
96
+ connector = None
97
+ if ssl_context:
98
+ connector = aiohttp.TCPConnector(ssl=ssl_context)
99
+
100
+ # Send registration request
101
+ async with aiohttp.ClientSession(connector=connector) as session:
102
+ register_url = f"{self.proxy_url}/register"
103
+ self.logger.info(f"Attempting to register server with proxy at {register_url}")
104
+ self.logger.debug(f"Registration data: {registration_data}")
105
+ self.logger.debug(f"Headers: {headers}")
106
+
107
+ # Ensure Content-Type header is set
108
+ if "Content-Type" not in headers:
109
+ headers["Content-Type"] = "application/json"
110
+
111
+ async with session.post(
112
+ register_url,
113
+ json=registration_data,
114
+ headers=headers,
115
+ timeout=aiohttp.ClientTimeout(total=30)
116
+ ) as response:
117
+ if response.status == 200:
118
+ result = await response.json()
119
+ self.logger.info(f"✅ Successfully registered with proxy. Server key: {result.get('key')}")
120
+ return True
121
+ else:
122
+ error_text = await response.text()
123
+ self.logger.error(
124
+ f"❌ Failed to register with proxy: {response.status} {response.reason}: {error_text}"
125
+ )
126
+ return False
127
+
128
+ except Exception as e:
129
+ self.logger.error(f"Registration error: {e}", exc_info=True)
130
+ return False
131
+
132
+ async def unregister(self) -> bool:
133
+ """
134
+ Unregister server from proxy.
135
+
136
+ Returns:
137
+ True if unregistration successful, False otherwise
138
+ """
139
+ try:
140
+ server_name = (
141
+ self.registration_config.get("server_id")
142
+ or self.registration_config.get("server_name")
143
+ or "mcp_proxy_adapter"
144
+ )
145
+
146
+ unregister_data = {
147
+ "name": server_name,
148
+ "url": "", # Not needed for unregister
149
+ "capabilities": [],
150
+ "metadata": {},
151
+ }
152
+
153
+ # Get SSL context if needed
154
+ ssl_context = self.ssl_manager.get_ssl_context()
155
+
156
+ # Get headers with authentication if needed
157
+ headers = self.auth_manager.get_headers()
158
+
159
+ # Prepare request configuration
160
+ connector = None
161
+ if ssl_context:
162
+ connector = aiohttp.TCPConnector(ssl=ssl_context)
163
+
164
+ # Send unregistration request
165
+ async with aiohttp.ClientSession(connector=connector) as session:
166
+ unregister_url = f"{self.proxy_url}/unregister"
167
+
168
+ async with session.post(
169
+ unregister_url,
170
+ json=unregister_data,
171
+ headers=headers,
172
+ timeout=aiohttp.ClientTimeout(total=10)
173
+ ) as response:
174
+ if response.status == 200:
175
+ self.logger.info("✅ Successfully unregistered from proxy")
176
+ return True
177
+ else:
178
+ error_text = await response.text()
179
+ self.logger.warning(
180
+ f"⚠️ Failed to unregister from proxy: {response.status} {response.reason}: {error_text}"
181
+ )
182
+ return False
183
+
184
+ except Exception as e:
185
+ self.logger.error(f"Unregistration error: {e}", exc_info=True)
186
+ return False
@@ -0,0 +1,101 @@
1
+ """
2
+ Author: Vasiliy Zdanovskiy
3
+ email: vasilyvz@gmail.com
4
+
5
+ SSL management for proxy registration.
6
+ """
7
+
8
+ import ssl
9
+ from typing import Dict, Any, Optional
10
+ from urllib.parse import urlparse
11
+
12
+ from mcp_proxy_adapter.core.logging import get_global_logger
13
+
14
+
15
+ class SSLManager:
16
+ """Manager for SSL connections in proxy registration."""
17
+
18
+ def __init__(self, client_security, registration_config: Dict[str, Any], config: Dict[str, Any], proxy_url: str):
19
+ """
20
+ Initialize SSL manager.
21
+
22
+ Args:
23
+ client_security: Client security manager instance
24
+ registration_config: Registration configuration
25
+ config: Application configuration
26
+ proxy_url: Proxy server URL
27
+ """
28
+ self.client_security = client_security
29
+ self.registration_config = registration_config
30
+ self.config = config
31
+ self.proxy_url = proxy_url
32
+ self.logger = get_global_logger()
33
+
34
+ def create_ssl_context(self) -> Optional[ssl.SSLContext]:
35
+ """
36
+ Create SSL context for secure connections using registration SSL configuration.
37
+
38
+ Returns:
39
+ SSL context or None if SSL not needed
40
+ """
41
+ self.logger.debug("_create_ssl_context called")
42
+
43
+ # Decide SSL strictly by proxy URL scheme: use SSL only for https proxy URLs
44
+ try:
45
+ scheme = urlparse(self.proxy_url).scheme if self.proxy_url else "http"
46
+ if scheme.lower() != "https":
47
+ self.logger.debug("Proxy URL is HTTP, skipping SSL context creation for registration")
48
+ return None
49
+ except Exception:
50
+ self.logger.debug("Failed to parse proxy_url, assuming HTTP and skipping SSL context")
51
+ return None
52
+
53
+ if not self.client_security:
54
+ self.logger.debug("SSL context creation failed: client_security is None")
55
+ return None
56
+
57
+ try:
58
+ # Check if SSL is enabled for registration
59
+ cert_config = self.registration_config.get("certificate", {})
60
+ ssl_config = self.registration_config.get("ssl", {})
61
+
62
+ # FALLBACK: if no explicit registration SSL/certs provided, reuse global SSL config
63
+ if not cert_config and not ssl_config:
64
+ global_ssl = self.config.get("security", {}).get("ssl", {}) or self.config.get("ssl", {})
65
+ if global_ssl:
66
+ # Map global ssl to registration-style configs
67
+ mapped_cert = {}
68
+ if global_ssl.get("cert_file") and global_ssl.get("key_file"):
69
+ mapped_cert = {
70
+ "cert_file": global_ssl.get("cert_file"),
71
+ "key_file": global_ssl.get("key_file"),
72
+ }
73
+ mapped_ssl = {}
74
+ if global_ssl.get("ca_cert"):
75
+ mapped_ssl["ca_cert"] = global_ssl.get("ca_cert")
76
+ if global_ssl.get("verify_client") is not None:
77
+ mapped_ssl["verify_mode"] = (
78
+ "CERT_REQUIRED" if global_ssl.get("verify_client") else "CERT_NONE"
79
+ )
80
+ cert_config = mapped_cert
81
+ ssl_config = mapped_ssl
82
+
83
+ # Use client security manager to create SSL context
84
+ if cert_config or ssl_config:
85
+ ssl_context = self.client_security.create_ssl_context(
86
+ cert_config=cert_config,
87
+ ssl_config=ssl_config
88
+ )
89
+ if ssl_context:
90
+ self.logger.debug("SSL context created successfully for registration")
91
+ return ssl_context
92
+ else:
93
+ self.logger.warning("Failed to create SSL context for registration")
94
+ return None
95
+ else:
96
+ self.logger.debug("No SSL configuration found for registration")
97
+ return None
98
+
99
+ except Exception as e:
100
+ self.logger.error(f"Error creating SSL context for registration: {e}")
101
+ return None
@@ -0,0 +1,184 @@
1
+ """
2
+ Author: Vasiliy Zdanovskiy
3
+ email: vasilyvz@gmail.com
4
+
5
+ Core mTLS Proxy Client.
6
+
7
+ Provides an asynchronous client for communicating with a proxy-like server over
8
+ mutual TLS. Designed to be used by services built on this framework to:
9
+ - perform health/heartbeat checks
10
+ - register themselves with the proxy
11
+
12
+ This client intentionally avoids framework-specific configuration objects and
13
+ accepts explicit parameters for clarity and portability.
14
+ """
15
+
16
+ import json
17
+ import ssl
18
+ from dataclasses import dataclass
19
+ from typing import Any, Dict, Optional, Tuple
20
+ from urllib.parse import urljoin
21
+
22
+ import aiohttp # type: ignore[import]
23
+
24
+
25
+ @dataclass(frozen=True)
26
+ class RegistrationRequest:
27
+ """Data payload for registration calls to the proxy server."""
28
+
29
+ server_id: str
30
+ server_name: str
31
+ description: Optional[str] = None
32
+ extra: Optional[Dict[str, Any]] = None
33
+
34
+ def to_dict(self) -> Dict[str, Any]:
35
+ payload: Dict[str, Any] = {
36
+ "server_id": self.server_id,
37
+ "server_name": self.server_name,
38
+ }
39
+ if self.description is not None:
40
+ payload["description"] = self.description
41
+ if self.extra:
42
+ payload.update(self.extra)
43
+ return payload
44
+
45
+
46
+ class ProxyClient:
47
+ """Asynchronous mTLS HTTP client for communicating with a proxy server.
48
+
49
+ Usage:
50
+ async with ProxyClient(
51
+ base_url="https://your-proxy-host:3004",
52
+ ca_cert_path="/path/to/ca.crt",
53
+ client_cert_path="/path/to/client.crt",
54
+ client_key_path="/path/to/client.key",
55
+ ) as client:
56
+ status, health = await client.health()
57
+ status, hb = await client.heartbeat()
58
+ status, reg = await client.register(
59
+ RegistrationRequest(...)
60
+ )
61
+ """
62
+
63
+ def __init__(
64
+ self,
65
+ base_url: str,
66
+ *,
67
+ ca_cert_path: str,
68
+ client_cert_path: str,
69
+ client_key_path: str,
70
+ request_timeout_s: float = 5.0,
71
+ min_tls_version: ssl.TLSVersion = ssl.TLSVersion.TLSv1_2,
72
+ verify_mode: ssl.VerifyMode = ssl.CERT_REQUIRED,
73
+ ) -> None:
74
+ if not base_url.startswith("http"):
75
+ raise ValueError("base_url must start with http/https")
76
+ self._base_url: str = base_url.rstrip("/")
77
+ self._ca_cert_path: str = ca_cert_path
78
+ self._client_cert_path: str = client_cert_path
79
+ self._client_key_path: str = client_key_path
80
+ self._request_timeout_s: float = request_timeout_s
81
+ self._min_tls_version: ssl.TLSVersion = min_tls_version
82
+ self._verify_mode: ssl.VerifyMode = verify_mode
83
+
84
+ self._ssl_context: Optional[ssl.SSLContext] = None
85
+ self._session: Optional[aiohttp.ClientSession] = None
86
+
87
+ async def __aenter__(self) -> "ProxyClient":
88
+ await self._ensure_session()
89
+ return self
90
+
91
+ async def __aexit__(self, exc_type, exc, tb) -> None:
92
+ await self.close()
93
+
94
+ async def _ensure_session(self) -> None:
95
+ if self._session is not None:
96
+ return
97
+ ssl_context = self._build_ssl_context()
98
+ timeout = aiohttp.ClientTimeout(total=self._request_timeout_s)
99
+ connector = aiohttp.TCPConnector(ssl=ssl_context)
100
+ self._session = aiohttp.ClientSession(
101
+ timeout=timeout,
102
+ connector=connector,
103
+ )
104
+ self._ssl_context = ssl_context
105
+
106
+ async def close(self) -> None:
107
+ if self._session is not None:
108
+ await self._session.close()
109
+ self._session = None
110
+ self._ssl_context = None
111
+
112
+ def _build_ssl_context(self) -> ssl.SSLContext:
113
+ ctx = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
114
+ ctx.minimum_version = self._min_tls_version
115
+ ctx.verify_mode = self._verify_mode
116
+ ctx.load_verify_locations(self._ca_cert_path)
117
+ ctx.load_cert_chain(self._client_cert_path, self._client_key_path)
118
+ return ctx
119
+
120
+ async def _get_json(self, path: str) -> Tuple[int, Dict[str, Any]]:
121
+ await self._ensure_session()
122
+ assert self._session is not None
123
+ url = urljoin(self._base_url + "/", path.lstrip("/"))
124
+ async with self._session.get(url) as resp:
125
+ status = resp.status
126
+ body_text = await resp.text()
127
+ try:
128
+ data: Dict[str, Any] = (
129
+ json.loads(body_text) if body_text else {}
130
+ )
131
+ except json.JSONDecodeError:
132
+ data = {"raw": body_text}
133
+ return status, data
134
+
135
+ async def _post_json(
136
+ self,
137
+ path: str,
138
+ payload: Dict[str, Any],
139
+ ) -> Tuple[int, Dict[str, Any]]:
140
+ await self._ensure_session()
141
+ assert self._session is not None
142
+ url = urljoin(self._base_url + "/", path.lstrip("/"))
143
+ headers = {"Content-Type": "application/json"}
144
+ async with self._session.post(
145
+ url,
146
+ headers=headers,
147
+ json=payload,
148
+ ) as resp:
149
+ status = resp.status
150
+ body_text = await resp.text()
151
+ try:
152
+ data: Dict[str, Any] = (
153
+ json.loads(body_text) if body_text else {}
154
+ )
155
+ except json.JSONDecodeError:
156
+ data = {"raw": body_text}
157
+ return status, data
158
+
159
+ async def health(
160
+ self,
161
+ path: str = "/health",
162
+ ) -> Tuple[int, Dict[str, Any]]:
163
+ """Perform a health check against the proxy."""
164
+ return await self._get_json(path)
165
+
166
+ async def heartbeat(
167
+ self,
168
+ path: str = "/heartbeat",
169
+ ) -> Tuple[int, Dict[str, Any]]:
170
+ """Perform a heartbeat (liveness) check against the proxy."""
171
+ return await self._get_json(path)
172
+
173
+ async def register(
174
+ self,
175
+ request: RegistrationRequest,
176
+ *,
177
+ path: str = "/register",
178
+ ) -> Tuple[int, Dict[str, Any]]:
179
+ """Register the current service on the proxy server."""
180
+ payload = request.to_dict()
181
+ return await self._post_json(
182
+ path,
183
+ payload,
184
+ )