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,356 @@
1
+ """Registration helper context builders for proxy interactions.
2
+
3
+ Author: Vasiliy Zdanovskiy
4
+ email: vasilyvz@gmail.com
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from dataclasses import dataclass
10
+ from pathlib import Path
11
+ from typing import Any, Dict, List, Optional, Tuple, Union
12
+
13
+
14
+ @dataclass
15
+ class ProxyCredentials:
16
+ """Client certificate and verification settings for proxy communication."""
17
+
18
+ cert: Optional[Tuple[str, str]]
19
+ verify: Union[bool, str]
20
+
21
+
22
+ @dataclass
23
+ class RegistrationContext:
24
+ """Prepared data required to register the adapter with a proxy."""
25
+
26
+ server_name: str
27
+ advertised_url: str
28
+ proxy_url: str
29
+ register_endpoint: str
30
+ capabilities: List[str]
31
+ metadata: Dict[str, Any]
32
+ use_proxy_client: bool
33
+ proxy_client_config: Dict[str, Any]
34
+ proxy_registration_config: Dict[str, Any]
35
+ credentials: ProxyCredentials
36
+
37
+
38
+ @dataclass
39
+ class HeartbeatSettings:
40
+ """Configuration for heartbeat scheduling."""
41
+
42
+ interval: int
43
+ endpoint: str
44
+
45
+
46
+ def prepare_registration_context(
47
+ config: Dict[str, Any], logger: Any
48
+ ) -> Optional[RegistrationContext]:
49
+ """Build registration context from configuration.
50
+
51
+ Returns ``None`` when registration should not be performed.
52
+ """
53
+
54
+ proxy_client_config = dict(config.get("proxy_client") or {})
55
+ proxy_registration_config = dict(config.get("proxy_registration") or {})
56
+ use_proxy_client = bool(proxy_client_config)
57
+
58
+ if use_proxy_client:
59
+ registration_config = proxy_client_config.get("registration") or {}
60
+ registration_enabled = registration_config.get("auto_on_startup", False)
61
+ else:
62
+ registration_config = proxy_registration_config
63
+ registration_enabled = proxy_registration_config.get("enabled", False)
64
+
65
+ if not registration_enabled:
66
+ logger.info(
67
+ "Proxy registration disabled (auto_on_startup=false or enabled=false)"
68
+ )
69
+ return None
70
+
71
+ if use_proxy_client:
72
+ proxy_host = proxy_client_config.get("host", "localhost")
73
+ proxy_port = proxy_client_config.get("port", 3005)
74
+ # Determine proxy scheme based on proxy protocol
75
+ # NOTE: proxy_client.protocol indicates the SERVER's protocol, not the proxy's protocol
76
+ # Determine proxy protocol:
77
+ # 1. Check explicit proxy_protocol field (if exists) - this is the proxy's actual protocol
78
+ # 2. Check if port is 3005 (test proxy) - always use HTTP for test proxy
79
+ # 3. Check if client certificates are provided AND exist on disk (indicates HTTPS/mTLS proxy)
80
+ # 4. Check server protocol as hint (if server uses HTTPS/mTLS, proxy might too)
81
+ # 5. Fallback to HTTP (for test proxy)
82
+ proxy_protocol = proxy_client_config.get("proxy_protocol")
83
+ if not proxy_protocol:
84
+ # Test proxy (port 3005) always uses HTTP
85
+ if proxy_port == 3005:
86
+ proxy_protocol = "http"
87
+ else:
88
+ # Check if client certificates are provided for proxy connection
89
+ cert_file = proxy_client_config.get("cert_file")
90
+ key_file = proxy_client_config.get("key_file")
91
+ ca_cert_file = proxy_client_config.get("ca_cert_file")
92
+ cert_tuple = _build_cert_tuple(cert_file, key_file)
93
+
94
+ if cert_tuple:
95
+ # Certificates exist and are valid - proxy uses HTTPS/mTLS
96
+ # Certificates in proxy_client are for connecting TO the proxy
97
+ # Check if CA cert is provided to determine if it's mTLS
98
+ if ca_cert_file and Path(ca_cert_file).exists():
99
+ # All certificates (cert, key, CA) provided - likely mTLS
100
+ # Use server protocol as hint for mTLS vs HTTPS
101
+ server_protocol = proxy_client_config.get("protocol", "http")
102
+ proxy_protocol = (
103
+ "mtls" if server_protocol == "mtls" else "https"
104
+ )
105
+ else:
106
+ # Only cert and key, no CA - HTTPS
107
+ proxy_protocol = "https"
108
+ else:
109
+ # No valid certificates - check server protocol as hint
110
+ server_protocol = proxy_client_config.get("protocol", "http")
111
+ if server_protocol in ("https", "mtls"):
112
+ # Server uses HTTPS/mTLS - proxy might too, but no certs provided
113
+ # Default to HTTP for safety (test proxy scenario)
114
+ proxy_protocol = "http"
115
+ else:
116
+ # Server uses HTTP - proxy likely uses HTTP too
117
+ proxy_protocol = "http"
118
+ # If proxy uses HTTPS/mTLS, use https scheme
119
+ proxy_scheme = "https" if proxy_protocol in ("https", "mtls") else "http"
120
+ proxy_base_url = f"{proxy_scheme}://{proxy_host}:{proxy_port}"
121
+ register_endpoint = registration_config.get("register_endpoint", "/register")
122
+ if register_endpoint.startswith("/proxy/"):
123
+ proxy_url = f"{proxy_base_url}/proxy"
124
+ else:
125
+ proxy_url = proxy_base_url
126
+ else:
127
+ proxy_url_candidate = (
128
+ proxy_registration_config.get("proxy_url")
129
+ or proxy_registration_config.get("server_url")
130
+ )
131
+ if not proxy_url_candidate:
132
+ logger.warning("No proxy server URL configured")
133
+ return None
134
+ proxy_url = str(proxy_url_candidate)
135
+ register_endpoint = "/register"
136
+
137
+ server_config = dict(config.get("server") or {})
138
+ host = server_config.get("host", "127.0.0.1")
139
+ port = server_config.get("port", 8000)
140
+ protocol = server_config.get("protocol", "http")
141
+ advertised_host = server_config.get("advertised_host") or host
142
+ scheme = "https" if protocol in ("https", "mtls") else "http"
143
+ advertised_url = f"{scheme}://{advertised_host}:{port}"
144
+
145
+ if use_proxy_client:
146
+ server_name = proxy_client_config.get("server_id") or proxy_client_config.get(
147
+ "server_name"
148
+ )
149
+ server_name = server_name or f"mcp-adapter-{host}-{port}"
150
+ capabilities = proxy_client_config.get("capabilities", ["jsonrpc", "health"])
151
+ metadata = {
152
+ "uuid": config.get("uuid"),
153
+ "protocol": protocol,
154
+ "host": host,
155
+ "port": port,
156
+ **(proxy_client_config.get("metadata") or {}),
157
+ }
158
+ else:
159
+ server_name = proxy_registration_config.get(
160
+ "server_id"
161
+ ) or proxy_registration_config.get("server_name")
162
+ server_name = server_name or f"mcp-adapter-{host}-{port}"
163
+ capabilities = proxy_registration_config.get(
164
+ "capabilities", ["jsonrpc", "health"]
165
+ )
166
+ metadata = {
167
+ "uuid": config.get("uuid"),
168
+ "protocol": protocol,
169
+ "host": host,
170
+ "port": port,
171
+ **(proxy_registration_config.get("metadata") or {}),
172
+ }
173
+
174
+ credentials = _resolve_registration_credentials(
175
+ use_proxy_client, proxy_client_config, proxy_registration_config
176
+ )
177
+
178
+ return RegistrationContext(
179
+ server_name=server_name,
180
+ advertised_url=advertised_url,
181
+ proxy_url=proxy_url,
182
+ register_endpoint=register_endpoint,
183
+ capabilities=capabilities,
184
+ metadata=metadata,
185
+ use_proxy_client=use_proxy_client,
186
+ proxy_client_config=proxy_client_config,
187
+ proxy_registration_config=proxy_registration_config,
188
+ credentials=credentials,
189
+ )
190
+
191
+
192
+ def resolve_runtime_credentials(
193
+ use_proxy_client: bool,
194
+ proxy_client_config: Dict[str, Any],
195
+ proxy_registration_config: Dict[str, Any],
196
+ ) -> ProxyCredentials:
197
+ """Return credentials for runtime interactions (heartbeat, unregister)."""
198
+
199
+ if use_proxy_client:
200
+ cert_tuple = _build_cert_tuple(
201
+ proxy_client_config.get("cert_file"),
202
+ proxy_client_config.get("key_file"),
203
+ )
204
+ proxy_protocol = proxy_client_config.get("protocol", "http")
205
+ ca_cert = proxy_client_config.get("ca_cert_file")
206
+
207
+ verify: Union[bool, str] = True
208
+ if proxy_protocol == "http":
209
+ verify = False
210
+ elif ca_cert:
211
+ verify = ca_cert
212
+
213
+ return ProxyCredentials(cert=cert_tuple, verify=verify)
214
+
215
+ return _resolve_registration_credentials(False, {}, proxy_registration_config)
216
+
217
+
218
+ def resolve_heartbeat_settings(
219
+ use_proxy_client: bool,
220
+ proxy_client_config: Dict[str, Any],
221
+ proxy_registration_config: Dict[str, Any],
222
+ ) -> HeartbeatSettings:
223
+ """Compute heartbeat interval and endpoint."""
224
+
225
+ if use_proxy_client:
226
+ heartbeat_config = proxy_client_config.get("heartbeat") or {}
227
+ interval = int(heartbeat_config.get("interval", 30))
228
+ endpoint = heartbeat_config.get("endpoint", "/heartbeat")
229
+ else:
230
+ heartbeat_config = proxy_registration_config.get("heartbeat") or {}
231
+ interval = int(
232
+ heartbeat_config.get(
233
+ "interval", proxy_registration_config.get("heartbeat_interval", 30)
234
+ )
235
+ )
236
+ endpoint = "/heartbeat"
237
+
238
+ return HeartbeatSettings(interval=interval, endpoint=endpoint)
239
+
240
+
241
+ def resolve_unregister_endpoint(
242
+ use_proxy_client: bool,
243
+ proxy_client_config: Dict[str, Any],
244
+ ) -> str:
245
+ """Get unregister endpoint path."""
246
+
247
+ if use_proxy_client:
248
+ registration_config = proxy_client_config.get("registration") or {}
249
+ endpoint = registration_config.get("unregister_endpoint")
250
+ if endpoint:
251
+ return str(endpoint)
252
+ return "/unregister"
253
+ return "/unregister"
254
+
255
+
256
+ def _resolve_registration_credentials(
257
+ use_proxy_client: bool,
258
+ proxy_client_config: Dict[str, Any],
259
+ proxy_registration_config: Dict[str, Any],
260
+ ) -> ProxyCredentials:
261
+ if use_proxy_client:
262
+ # Use same logic as resolve_runtime_credentials for consistency
263
+ # Check if proxy itself uses HTTPS/mTLS and requires certificates
264
+ cert_tuple = _build_cert_tuple(
265
+ proxy_client_config.get("cert_file"),
266
+ proxy_client_config.get("key_file"),
267
+ )
268
+ # Determine proxy protocol (same logic as in prepare_registration_context)
269
+ proxy_protocol = proxy_client_config.get("proxy_protocol")
270
+ if not proxy_protocol:
271
+ # Get proxy port for test proxy detection
272
+ proxy_port = proxy_client_config.get("port", 3005)
273
+ # Test proxy (port 3005) always uses HTTP
274
+ if proxy_port == 3005:
275
+ proxy_protocol = "http"
276
+ else:
277
+ # Check if client certificates are provided for proxy connection
278
+ cert_file = proxy_client_config.get("cert_file")
279
+ key_file = proxy_client_config.get("key_file")
280
+ ca_cert_file = proxy_client_config.get("ca_cert_file")
281
+ cert_tuple = _build_cert_tuple(cert_file, key_file)
282
+
283
+ if cert_tuple:
284
+ # Certificates exist and are valid - proxy uses HTTPS/mTLS
285
+ # Check if CA cert is provided to determine if it's mTLS
286
+ if ca_cert_file and Path(ca_cert_file).exists():
287
+ # All certificates (cert, key, CA) provided - likely mTLS
288
+ # Use server protocol as hint for mTLS vs HTTPS
289
+ server_protocol = proxy_client_config.get("protocol", "http")
290
+ proxy_protocol = (
291
+ "mtls" if server_protocol == "mtls" else "https"
292
+ )
293
+ else:
294
+ # Only cert and key, no CA - HTTPS
295
+ proxy_protocol = "https"
296
+ else:
297
+ # No valid certificates - check server protocol as hint
298
+ server_protocol = proxy_client_config.get("protocol", "http")
299
+ if server_protocol in ("https", "mtls"):
300
+ # Server uses HTTPS/mTLS - proxy might too, but no certs provided
301
+ # Default to HTTP for safety (test proxy scenario)
302
+ proxy_protocol = "http"
303
+ else:
304
+ # Server uses HTTP - proxy likely uses HTTP too
305
+ proxy_protocol = "http"
306
+ ca_cert = proxy_client_config.get("ca_cert_file")
307
+
308
+ verify: Union[bool, str] = True
309
+ if proxy_protocol == "http":
310
+ # HTTP proxy doesn't need certificates
311
+ verify = False
312
+ elif proxy_protocol in ("https", "mtls"):
313
+ # HTTPS/mTLS proxy requires certificates
314
+ if ca_cert:
315
+ verify = ca_cert
316
+ elif proxy_protocol == "mtls":
317
+ # mTLS requires CA cert
318
+ verify = True # Will fail if no CA, but that's expected
319
+ else:
320
+ # HTTPS can use system CA store
321
+ verify = True
322
+
323
+ return ProxyCredentials(cert=cert_tuple, verify=verify)
324
+
325
+ cert_config = proxy_registration_config.get("certificate") or {}
326
+ ssl_config = proxy_registration_config.get("ssl") or {}
327
+
328
+ cert_tuple = _build_cert_tuple(
329
+ cert_config.get("cert_file"),
330
+ cert_config.get("key_file"),
331
+ )
332
+
333
+ verify: Union[bool, str] = True
334
+ ca_cert = ssl_config.get("ca_cert")
335
+ verify_mode = ssl_config.get("verify_mode", "CERT_REQUIRED")
336
+ if verify_mode == "CERT_NONE":
337
+ verify = False
338
+ elif ca_cert:
339
+ verify = ca_cert
340
+
341
+ return ProxyCredentials(cert=cert_tuple, verify=verify)
342
+
343
+
344
+ def _build_cert_tuple(
345
+ cert_file: Optional[str],
346
+ key_file: Optional[str],
347
+ ) -> Optional[Tuple[str, str]]:
348
+ if not cert_file or not key_file:
349
+ return None
350
+
351
+ cert_path = Path(cert_file)
352
+ key_path = Path(key_file)
353
+ if not cert_path.exists() or not key_path.exists():
354
+ return None
355
+
356
+ return (str(cert_path.absolute()), str(key_path.absolute()))
@@ -0,0 +1,307 @@
1
+ """
2
+ Author: Vasiliy Zdanovskiy
3
+ email: vasilyvz@gmail.com
4
+
5
+ Registration management utilities for MCP Proxy Adapter API.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import asyncio
11
+ from typing import Any, Dict, List, Optional
12
+
13
+ from mcp_proxy_adapter.client.jsonrpc_client import JsonRpcClient
14
+ from mcp_proxy_adapter.core.logging import get_global_logger
15
+
16
+ from mcp_proxy_adapter.api.core.registration_context import (
17
+ HeartbeatSettings,
18
+ ProxyCredentials,
19
+ RegistrationContext,
20
+ prepare_registration_context,
21
+ resolve_heartbeat_settings,
22
+ resolve_runtime_credentials,
23
+ resolve_unregister_endpoint,
24
+ )
25
+ from mcp_proxy_adapter.api.core.registration_tasks import (
26
+ create_heartbeat_task,
27
+ unregister_from_proxy as unregister_task,
28
+ )
29
+
30
+
31
+ class RegistrationManager:
32
+ """Manager for proxy registration functionality using JsonRpcClient."""
33
+
34
+ def __init__(self) -> None:
35
+ """Initialize registration manager."""
36
+ self.logger = get_global_logger()
37
+ self.registered = False
38
+ self.registration_task: Optional[asyncio.Task] = None
39
+ self.server_name: Optional[str] = None
40
+ self.server_url: Optional[str] = None
41
+ self.proxy_url: Optional[str] = None
42
+ self.capabilities: List[str] = []
43
+ self.metadata: Dict[str, Any] = {}
44
+ self.config: Optional[Dict[str, Any]] = None
45
+ self._use_proxy_client = False
46
+ self._proxy_client_config: Dict[str, Any] = {}
47
+ self._proxy_registration_config: Dict[str, Any] = {}
48
+ self._registration_credentials: Optional[ProxyCredentials] = None
49
+ self._runtime_credentials: Optional[ProxyCredentials] = None
50
+ self._register_endpoint: str = "/register"
51
+ self._heartbeat_settings: Optional[HeartbeatSettings] = None
52
+
53
+ async def register_with_proxy(self, config: Dict[str, Any]) -> bool:
54
+ """
55
+ Register this server with the proxy using JsonRpcClient.
56
+
57
+ Supports both ``proxy_client`` (SimpleConfig format) and ``proxy_registration`` (legacy format).
58
+ Registration is controlled by ``registration.auto_on_startup`` rather than ``proxy_client.enabled``.
59
+ """
60
+
61
+ context = prepare_registration_context(config, self.logger)
62
+ if context is None:
63
+ return True
64
+
65
+ self._apply_context(context, config)
66
+
67
+ proxy_url = self.proxy_url
68
+ assert proxy_url is not None
69
+ assert self.server_name is not None
70
+ assert self.server_url is not None
71
+
72
+ # Parse proxy URL to extract protocol, host, and port
73
+ from urllib.parse import urlparse
74
+
75
+ parsed = urlparse(proxy_url)
76
+ proxy_protocol = parsed.scheme or "http"
77
+ proxy_host = parsed.hostname or "localhost"
78
+ proxy_port = parsed.port or (443 if proxy_protocol == "https" else 80)
79
+
80
+ # Extract client certificate paths from credentials if available
81
+ client_cert = None
82
+ client_key = None
83
+ client_ca = None
84
+ if context.credentials.cert:
85
+ client_cert, client_key = context.credentials.cert
86
+ if isinstance(context.credentials.verify, str):
87
+ client_ca = context.credentials.verify
88
+
89
+ self.logger.debug(
90
+ "Creating JsonRpcClient: protocol=%s, host=%s, port=%s, cert=%s, ca=%s",
91
+ proxy_protocol,
92
+ proxy_host,
93
+ proxy_port,
94
+ client_cert is not None,
95
+ client_ca is not None,
96
+ )
97
+ if client_cert:
98
+ self.logger.debug(" Client cert: %s, key: %s", client_cert, client_key)
99
+ if client_ca:
100
+ self.logger.debug(" CA cert: %s", client_ca)
101
+
102
+ client = JsonRpcClient(
103
+ protocol=proxy_protocol,
104
+ host=proxy_host,
105
+ port=proxy_port,
106
+ cert=client_cert,
107
+ key=client_key,
108
+ ca=client_ca,
109
+ )
110
+
111
+ async def _register() -> Dict[str, Any]:
112
+ self._log_credentials("🔐 Registration SSL config", context.credentials)
113
+ self.logger.info(f"📡 Connecting to proxy: {proxy_url}")
114
+ self.logger.debug(
115
+ " Endpoint: %s, Server: %s -> %s",
116
+ self._register_endpoint,
117
+ self.server_name,
118
+ self.server_url,
119
+ )
120
+ # Note: cert and verify are already set in JsonRpcClient constructor
121
+ # but we pass them again for explicit override if needed
122
+ return await client.register_with_proxy(
123
+ proxy_url=proxy_url,
124
+ server_name=context.server_name,
125
+ server_url=context.advertised_url,
126
+ capabilities=self.capabilities,
127
+ metadata=self.metadata,
128
+ cert=context.credentials.cert,
129
+ verify=context.credentials.verify,
130
+ )
131
+
132
+ max_retries = 5
133
+ retry_delay = 2
134
+
135
+ try:
136
+ for attempt in range(max_retries):
137
+ try:
138
+ registration_response = await _register()
139
+ if registration_response is not None:
140
+ self.logger.debug(
141
+ "Proxy registration response payload: %s",
142
+ registration_response,
143
+ )
144
+ self.logger.info(
145
+ "✅ Successfully registered with proxy as %s -> %s",
146
+ self.server_name,
147
+ self.server_url,
148
+ )
149
+ self.registered = True
150
+ return True
151
+ except Exception as exc: # noqa: BLE001
152
+ full_error = self._format_httpx_error(exc)
153
+ if attempt < max_retries - 1:
154
+ self.logger.warning(
155
+ "⚠️ Registration attempt %s/%s failed: %s. Retrying in %ss...",
156
+ attempt + 1,
157
+ max_retries,
158
+ full_error,
159
+ retry_delay,
160
+ )
161
+ await asyncio.sleep(retry_delay)
162
+ retry_delay *= 2
163
+ else:
164
+ self.logger.error(
165
+ "❌ Failed to register with proxy after %s attempts: %s",
166
+ max_retries,
167
+ full_error,
168
+ )
169
+ return False
170
+ # If we get here, all retries failed
171
+ return False
172
+ except Exception as exc: # noqa: BLE001
173
+ self.logger.error(f"❌ Registration error: {exc}")
174
+ return False
175
+ finally:
176
+ await client.close()
177
+
178
+ async def start_heartbeat(self, _config: Dict[str, Any]) -> None:
179
+ """Start heartbeat task using JsonRpcClient."""
180
+
181
+ if not self._can_start_tasks() or not self.registered:
182
+ return
183
+
184
+ credentials = resolve_runtime_credentials(
185
+ self._use_proxy_client,
186
+ self._proxy_client_config,
187
+ self._proxy_registration_config,
188
+ )
189
+ settings = resolve_heartbeat_settings(
190
+ self._use_proxy_client,
191
+ self._proxy_client_config,
192
+ self._proxy_registration_config,
193
+ )
194
+ self._runtime_credentials = credentials
195
+ self._heartbeat_settings = settings
196
+
197
+ heartbeat_url = f"{self.proxy_url}{settings.endpoint}"
198
+ self.logger.info(
199
+ "💓 Starting heartbeat task (interval: %ss)", settings.interval
200
+ )
201
+
202
+ assert self.server_name is not None
203
+ assert self.server_url is not None
204
+
205
+ self.registration_task = create_heartbeat_task(
206
+ proxy_url=heartbeat_url,
207
+ server_name=self.server_name,
208
+ server_url=self.server_url,
209
+ capabilities=self.capabilities,
210
+ metadata=self.metadata,
211
+ settings=settings,
212
+ credentials=credentials,
213
+ logger=self.logger,
214
+ )
215
+
216
+ async def stop(self) -> None:
217
+ """Stop registration manager and unregister from proxy."""
218
+
219
+ if self.registration_task:
220
+ self.registration_task.cancel()
221
+ try:
222
+ await self.registration_task
223
+ except asyncio.CancelledError:
224
+ pass
225
+ self.registration_task = None
226
+
227
+ if not (self.registered and self._can_start_tasks() and self.config):
228
+ self.registered = False
229
+ return
230
+
231
+ credentials = self._runtime_credentials or resolve_runtime_credentials(
232
+ self._use_proxy_client,
233
+ self._proxy_client_config,
234
+ self._proxy_registration_config,
235
+ )
236
+ endpoint = resolve_unregister_endpoint(
237
+ self._use_proxy_client,
238
+ self._proxy_client_config,
239
+ )
240
+
241
+ assert self.proxy_url is not None
242
+ assert self.server_name is not None
243
+
244
+ try:
245
+ await unregister_task(
246
+ proxy_url=self.proxy_url,
247
+ server_name=self.server_name,
248
+ endpoint=endpoint,
249
+ credentials=credentials,
250
+ logger=self.logger,
251
+ )
252
+ except Exception as exc: # noqa: BLE001
253
+ self.logger.error(f"Error unregistering from proxy: {exc}")
254
+ finally:
255
+ self.registered = False
256
+
257
+ def _apply_context(
258
+ self, context: RegistrationContext, config: Dict[str, Any]
259
+ ) -> None:
260
+ self.server_name = context.server_name
261
+ self.server_url = context.advertised_url
262
+ self.proxy_url = context.proxy_url
263
+ self.capabilities = list(context.capabilities)
264
+ self.metadata = dict(context.metadata)
265
+ self.config = config
266
+ self._use_proxy_client = context.use_proxy_client
267
+ self._proxy_client_config = context.proxy_client_config
268
+ self._proxy_registration_config = context.proxy_registration_config
269
+ self._registration_credentials = context.credentials
270
+ self._runtime_credentials = None
271
+ self._register_endpoint = context.register_endpoint
272
+
273
+ def _log_credentials(self, prefix: str, credentials: ProxyCredentials) -> None:
274
+ self.logger.info(
275
+ "%s: cert=%s, verify=%s",
276
+ prefix,
277
+ credentials.cert is not None,
278
+ credentials.verify,
279
+ )
280
+ if credentials.cert:
281
+ self.logger.debug(" Client cert: %s, key: %s", *credentials.cert)
282
+ if isinstance(credentials.verify, str):
283
+ self.logger.debug(" CA cert: %s", credentials.verify)
284
+
285
+ def _format_httpx_error(self, exc: Exception) -> str:
286
+ import httpx
287
+
288
+ error_msg = str(exc) or type(exc).__name__
289
+ details: List[str] = [f"type={type(exc).__name__}"]
290
+
291
+ if isinstance(exc, httpx.HTTPStatusError):
292
+ details.append(f"status={exc.response.status_code}")
293
+ try:
294
+ details.append(f"response={exc.response.text[:200]}")
295
+ except Exception: # noqa: BLE001
296
+ pass
297
+ elif isinstance(exc, httpx.ConnectError):
298
+ details.append("connection_failed")
299
+ if hasattr(exc, "request"):
300
+ details.append(f"url={exc.request.url}")
301
+ elif isinstance(exc, httpx.TimeoutException):
302
+ details.append("timeout")
303
+
304
+ return f"{error_msg} ({', '.join(details)})" if details else error_msg
305
+
306
+ def _can_start_tasks(self) -> bool:
307
+ return bool(self.proxy_url and self.server_name and self.server_url)