mcp-proxy-adapter 6.9.27__py3-none-any.whl → 6.9.29__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 (212) hide show
  1. mcp_proxy_adapter/__init__.py +10 -0
  2. mcp_proxy_adapter/__main__.py +8 -21
  3. mcp_proxy_adapter/api/app.py +10 -913
  4. mcp_proxy_adapter/api/core/__init__.py +18 -0
  5. mcp_proxy_adapter/api/core/app_factory.py +243 -0
  6. mcp_proxy_adapter/api/core/lifespan_manager.py +55 -0
  7. mcp_proxy_adapter/api/core/registration_manager.py +166 -0
  8. mcp_proxy_adapter/api/core/ssl_context_factory.py +88 -0
  9. mcp_proxy_adapter/api/handlers.py +78 -199
  10. mcp_proxy_adapter/api/middleware/__init__.py +1 -44
  11. mcp_proxy_adapter/api/middleware/base.py +0 -42
  12. mcp_proxy_adapter/api/middleware/command_permission_middleware.py +0 -85
  13. mcp_proxy_adapter/api/middleware/error_handling.py +1 -127
  14. mcp_proxy_adapter/api/middleware/factory.py +0 -94
  15. mcp_proxy_adapter/api/middleware/logging.py +0 -112
  16. mcp_proxy_adapter/api/middleware/performance.py +0 -35
  17. mcp_proxy_adapter/api/middleware/protocol_middleware.py +2 -98
  18. mcp_proxy_adapter/api/middleware/transport_middleware.py +0 -37
  19. mcp_proxy_adapter/api/middleware/unified_security.py +10 -10
  20. mcp_proxy_adapter/api/middleware/user_info_middleware.py +0 -118
  21. mcp_proxy_adapter/api/openapi/__init__.py +21 -0
  22. mcp_proxy_adapter/api/openapi/command_integration.py +105 -0
  23. mcp_proxy_adapter/api/openapi/openapi_generator.py +40 -0
  24. mcp_proxy_adapter/api/openapi/openapi_registry.py +62 -0
  25. mcp_proxy_adapter/api/openapi/schema_loader.py +116 -0
  26. mcp_proxy_adapter/api/schemas.py +0 -61
  27. mcp_proxy_adapter/api/tool_integration.py +0 -117
  28. mcp_proxy_adapter/api/tools.py +0 -46
  29. mcp_proxy_adapter/cli/__init__.py +12 -0
  30. mcp_proxy_adapter/cli/commands/__init__.py +15 -0
  31. mcp_proxy_adapter/cli/commands/client.py +100 -0
  32. mcp_proxy_adapter/cli/commands/config_generate.py +21 -0
  33. mcp_proxy_adapter/cli/commands/config_validate.py +36 -0
  34. mcp_proxy_adapter/cli/commands/generate.py +259 -0
  35. mcp_proxy_adapter/cli/commands/server.py +174 -0
  36. mcp_proxy_adapter/cli/commands/sets.py +128 -0
  37. mcp_proxy_adapter/cli/commands/testconfig.py +177 -0
  38. mcp_proxy_adapter/cli/examples/__init__.py +8 -0
  39. mcp_proxy_adapter/cli/examples/http_basic.py +82 -0
  40. mcp_proxy_adapter/cli/examples/https_token.py +96 -0
  41. mcp_proxy_adapter/cli/examples/mtls_roles.py +103 -0
  42. mcp_proxy_adapter/cli/main.py +63 -0
  43. mcp_proxy_adapter/cli/parser.py +324 -0
  44. mcp_proxy_adapter/cli/validators.py +231 -0
  45. mcp_proxy_adapter/client/jsonrpc_client.py +406 -0
  46. mcp_proxy_adapter/client/proxy.py +45 -0
  47. mcp_proxy_adapter/commands/__init__.py +44 -28
  48. mcp_proxy_adapter/commands/auth_validation_command.py +7 -344
  49. mcp_proxy_adapter/commands/base.py +19 -43
  50. mcp_proxy_adapter/commands/builtin_commands.py +0 -75
  51. mcp_proxy_adapter/commands/catalog/__init__.py +20 -0
  52. mcp_proxy_adapter/commands/catalog/catalog_loader.py +34 -0
  53. mcp_proxy_adapter/commands/catalog/catalog_manager.py +122 -0
  54. mcp_proxy_adapter/commands/catalog/catalog_syncer.py +149 -0
  55. mcp_proxy_adapter/commands/catalog/command_catalog.py +43 -0
  56. mcp_proxy_adapter/commands/catalog/dependency_manager.py +37 -0
  57. mcp_proxy_adapter/commands/catalog_manager.py +58 -928
  58. mcp_proxy_adapter/commands/cert_monitor_command.py +0 -88
  59. mcp_proxy_adapter/commands/certificate_management_command.py +0 -45
  60. mcp_proxy_adapter/commands/command_registry.py +172 -904
  61. mcp_proxy_adapter/commands/config_command.py +0 -28
  62. mcp_proxy_adapter/commands/dependency_container.py +1 -70
  63. mcp_proxy_adapter/commands/dependency_manager.py +0 -128
  64. mcp_proxy_adapter/commands/echo_command.py +0 -34
  65. mcp_proxy_adapter/commands/health_command.py +0 -3
  66. mcp_proxy_adapter/commands/help_command.py +0 -159
  67. mcp_proxy_adapter/commands/hooks.py +0 -137
  68. mcp_proxy_adapter/commands/key_management_command.py +0 -25
  69. mcp_proxy_adapter/commands/load_command.py +7 -78
  70. mcp_proxy_adapter/commands/plugins_command.py +0 -16
  71. mcp_proxy_adapter/commands/protocol_management_command.py +0 -28
  72. mcp_proxy_adapter/commands/proxy_registration_command.py +0 -88
  73. mcp_proxy_adapter/commands/queue_commands.py +750 -0
  74. mcp_proxy_adapter/commands/registration_status_command.py +0 -43
  75. mcp_proxy_adapter/commands/registry/__init__.py +18 -0
  76. mcp_proxy_adapter/commands/registry/command_info.py +103 -0
  77. mcp_proxy_adapter/commands/registry/command_loader.py +207 -0
  78. mcp_proxy_adapter/commands/registry/command_manager.py +119 -0
  79. mcp_proxy_adapter/commands/registry/command_registry.py +217 -0
  80. mcp_proxy_adapter/commands/reload_command.py +0 -80
  81. mcp_proxy_adapter/commands/result.py +25 -77
  82. mcp_proxy_adapter/commands/role_test_command.py +0 -44
  83. mcp_proxy_adapter/commands/roles_management_command.py +0 -199
  84. mcp_proxy_adapter/commands/security_command.py +0 -30
  85. mcp_proxy_adapter/commands/settings_command.py +0 -68
  86. mcp_proxy_adapter/commands/ssl_setup_command.py +0 -42
  87. mcp_proxy_adapter/commands/token_management_command.py +0 -1
  88. mcp_proxy_adapter/commands/transport_management_command.py +0 -20
  89. mcp_proxy_adapter/commands/unload_command.py +0 -71
  90. mcp_proxy_adapter/config.py +15 -626
  91. mcp_proxy_adapter/core/__init__.py +5 -39
  92. mcp_proxy_adapter/core/app_factory.py +14 -36
  93. mcp_proxy_adapter/core/app_runner.py +0 -27
  94. mcp_proxy_adapter/core/auth_validator.py +1 -93
  95. mcp_proxy_adapter/core/certificate/__init__.py +20 -0
  96. mcp_proxy_adapter/core/certificate/certificate_creator.py +371 -0
  97. mcp_proxy_adapter/core/certificate/certificate_extractor.py +183 -0
  98. mcp_proxy_adapter/core/certificate/certificate_utils.py +249 -0
  99. mcp_proxy_adapter/core/certificate/certificate_validator.py +110 -0
  100. mcp_proxy_adapter/core/certificate/ssl_context_manager.py +70 -0
  101. mcp_proxy_adapter/core/certificate_utils.py +64 -903
  102. mcp_proxy_adapter/core/client.py +0 -6
  103. mcp_proxy_adapter/core/client_manager.py +0 -19
  104. mcp_proxy_adapter/core/client_security.py +0 -2
  105. mcp_proxy_adapter/core/config/__init__.py +18 -0
  106. mcp_proxy_adapter/core/config/config.py +195 -0
  107. mcp_proxy_adapter/core/config/config_factory.py +22 -0
  108. mcp_proxy_adapter/core/config/config_loader.py +66 -0
  109. mcp_proxy_adapter/core/config/feature_manager.py +31 -0
  110. mcp_proxy_adapter/core/config/simple_config.py +112 -0
  111. mcp_proxy_adapter/core/config/simple_config_generator.py +50 -0
  112. mcp_proxy_adapter/core/config/simple_config_validator.py +96 -0
  113. mcp_proxy_adapter/core/config_converter.py +0 -186
  114. mcp_proxy_adapter/core/config_validator.py +96 -1238
  115. mcp_proxy_adapter/core/errors.py +7 -42
  116. mcp_proxy_adapter/core/job_manager.py +54 -0
  117. mcp_proxy_adapter/core/logging.py +2 -22
  118. mcp_proxy_adapter/core/mtls_asgi.py +0 -20
  119. mcp_proxy_adapter/core/mtls_asgi_app.py +0 -12
  120. mcp_proxy_adapter/core/mtls_proxy.py +0 -80
  121. mcp_proxy_adapter/core/mtls_server.py +3 -173
  122. mcp_proxy_adapter/core/protocol_manager.py +1 -191
  123. mcp_proxy_adapter/core/proxy/__init__.py +22 -0
  124. mcp_proxy_adapter/core/proxy/auth_manager.py +27 -0
  125. mcp_proxy_adapter/core/proxy/proxy_registration_manager.py +137 -0
  126. mcp_proxy_adapter/core/proxy/registration_client.py +60 -0
  127. mcp_proxy_adapter/core/proxy/ssl_manager.py +101 -0
  128. mcp_proxy_adapter/core/proxy_client.py +0 -1
  129. mcp_proxy_adapter/core/proxy_registration.py +36 -912
  130. mcp_proxy_adapter/core/role_utils.py +0 -308
  131. mcp_proxy_adapter/core/security_adapter.py +1 -36
  132. mcp_proxy_adapter/core/security_factory.py +1 -150
  133. mcp_proxy_adapter/core/security_integration.py +0 -33
  134. mcp_proxy_adapter/core/server_adapter.py +1 -40
  135. mcp_proxy_adapter/core/server_engine.py +2 -173
  136. mcp_proxy_adapter/core/settings.py +0 -127
  137. mcp_proxy_adapter/core/signal_handler.py +0 -65
  138. mcp_proxy_adapter/core/ssl_utils.py +19 -137
  139. mcp_proxy_adapter/core/transport_manager.py +0 -151
  140. mcp_proxy_adapter/core/unified_config_adapter.py +1 -193
  141. mcp_proxy_adapter/core/utils.py +1 -182
  142. mcp_proxy_adapter/core/validation/__init__.py +21 -0
  143. mcp_proxy_adapter/core/validation/config_validator.py +211 -0
  144. mcp_proxy_adapter/core/validation/file_validator.py +73 -0
  145. mcp_proxy_adapter/core/validation/protocol_validator.py +191 -0
  146. mcp_proxy_adapter/core/validation/security_validator.py +58 -0
  147. mcp_proxy_adapter/core/validation/validation_result.py +27 -0
  148. mcp_proxy_adapter/custom_openapi.py +33 -652
  149. mcp_proxy_adapter/examples/bugfix_certificate_config.py +0 -23
  150. mcp_proxy_adapter/examples/check_config.py +0 -2
  151. mcp_proxy_adapter/examples/client_usage_example.py +164 -0
  152. mcp_proxy_adapter/examples/config_builder.py +13 -2
  153. mcp_proxy_adapter/examples/config_cli.py +0 -1
  154. mcp_proxy_adapter/examples/create_test_configs.py +0 -46
  155. mcp_proxy_adapter/examples/debug_request_state.py +0 -1
  156. mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +0 -47
  157. mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +0 -45
  158. mcp_proxy_adapter/examples/full_application/commands/echo_command.py +0 -12
  159. mcp_proxy_adapter/examples/full_application/commands/help_command.py +0 -12
  160. mcp_proxy_adapter/examples/full_application/commands/list_command.py +0 -7
  161. mcp_proxy_adapter/examples/full_application/hooks/__init__.py +0 -2
  162. mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +0 -59
  163. mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +0 -54
  164. mcp_proxy_adapter/examples/full_application/main.py +186 -150
  165. mcp_proxy_adapter/examples/full_application/proxy_endpoints.py +0 -107
  166. mcp_proxy_adapter/examples/full_application/test_minimal_server.py +0 -24
  167. mcp_proxy_adapter/examples/full_application/test_server.py +0 -58
  168. mcp_proxy_adapter/examples/generate_config.py +65 -11
  169. mcp_proxy_adapter/examples/queue_demo_simple.py +632 -0
  170. mcp_proxy_adapter/examples/queue_integration_example.py +578 -0
  171. mcp_proxy_adapter/examples/queue_server_demo.py +82 -0
  172. mcp_proxy_adapter/examples/queue_server_example.py +85 -0
  173. mcp_proxy_adapter/examples/queue_server_simple.py +173 -0
  174. mcp_proxy_adapter/examples/required_certificates.py +0 -2
  175. mcp_proxy_adapter/examples/run_full_test_suite.py +0 -29
  176. mcp_proxy_adapter/examples/run_proxy_server.py +31 -71
  177. mcp_proxy_adapter/examples/run_security_tests_fixed.py +0 -27
  178. mcp_proxy_adapter/examples/security_test/__init__.py +18 -0
  179. mcp_proxy_adapter/examples/security_test/auth_manager.py +14 -0
  180. mcp_proxy_adapter/examples/security_test/ssl_context_manager.py +28 -0
  181. mcp_proxy_adapter/examples/security_test/test_client.py +159 -0
  182. mcp_proxy_adapter/examples/security_test/test_result.py +22 -0
  183. mcp_proxy_adapter/examples/security_test_client.py +24 -1075
  184. mcp_proxy_adapter/examples/setup/__init__.py +24 -0
  185. mcp_proxy_adapter/examples/setup/certificate_manager.py +215 -0
  186. mcp_proxy_adapter/examples/setup/config_generator.py +12 -0
  187. mcp_proxy_adapter/examples/setup/config_validator.py +118 -0
  188. mcp_proxy_adapter/examples/setup/environment_setup.py +62 -0
  189. mcp_proxy_adapter/examples/setup/test_files_generator.py +10 -0
  190. mcp_proxy_adapter/examples/setup/test_runner.py +89 -0
  191. mcp_proxy_adapter/examples/setup_test_environment.py +133 -1425
  192. mcp_proxy_adapter/examples/test_config.py +0 -3
  193. mcp_proxy_adapter/examples/test_config_builder.py +25 -405
  194. mcp_proxy_adapter/examples/test_examples.py +0 -1
  195. mcp_proxy_adapter/examples/test_framework_complete.py +0 -2
  196. mcp_proxy_adapter/examples/test_mcp_server.py +0 -1
  197. mcp_proxy_adapter/examples/test_protocol_examples.py +0 -1
  198. mcp_proxy_adapter/examples/universal_client.py +0 -6
  199. mcp_proxy_adapter/examples/update_config_certificates.py +0 -1
  200. mcp_proxy_adapter/examples/validate_generator_compatibility.py +0 -1
  201. mcp_proxy_adapter/examples/validate_generator_compatibility_simple.py +0 -187
  202. mcp_proxy_adapter/integrations/__init__.py +25 -0
  203. mcp_proxy_adapter/integrations/queuemgr_integration.py +462 -0
  204. mcp_proxy_adapter/main.py +70 -62
  205. mcp_proxy_adapter/openapi.py +0 -22
  206. mcp_proxy_adapter/version.py +1 -1
  207. {mcp_proxy_adapter-6.9.27.dist-info → mcp_proxy_adapter-6.9.29.dist-info}/METADATA +2 -1
  208. mcp_proxy_adapter-6.9.29.dist-info/RECORD +235 -0
  209. {mcp_proxy_adapter-6.9.27.dist-info → mcp_proxy_adapter-6.9.29.dist-info}/entry_points.txt +1 -1
  210. mcp_proxy_adapter-6.9.27.dist-info/RECORD +0 -149
  211. {mcp_proxy_adapter-6.9.27.dist-info → mcp_proxy_adapter-6.9.29.dist-info}/WHEEL +0 -0
  212. {mcp_proxy_adapter-6.9.27.dist-info → mcp_proxy_adapter-6.9.29.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,3 @@
1
- # flake8: noqa: E501
2
1
  """
3
2
  Module for proxy registration functionality with security framework integration.
4
3
 
@@ -10,939 +9,57 @@ Author: Vasiliy Zdanovskiy
10
9
  email: vasilyvz@gmail.com
11
10
  """
12
11
 
13
- import asyncio
14
- import time
15
- import ssl
16
- import traceback
17
- from typing import Dict, Any, Optional, Tuple
18
- from pathlib import Path
19
- from urllib.parse import urljoin
12
+ from typing import Dict, Any
20
13
 
21
- import aiohttp
22
- import os
23
- import glob
14
+ ProxyRegistrationManager,
15
+ ProxyRegistrationError,
16
+ initialize_proxy_registration,
17
+ get_proxy_registration_status,
18
+ )
24
19
 
25
- from mcp_proxy_adapter.core.logging import get_global_logger
26
- from mcp_proxy_adapter.core.client_security import create_client_security_manager
27
-
28
-
29
- class ProxyRegistrationError(Exception):
30
- """Exception raised when proxy registration fails."""
31
-
32
- pass
33
-
34
-
35
- class ProxyRegistrationManager:
36
- """
37
- Manager for proxy registration functionality with security framework integration.
38
-
39
- Handles automatic registration and unregistration of the server
40
- with the MCP proxy server using secure authentication methods.
41
- """
42
-
43
- def __init__(self, config: Dict[str, Any]):
44
- """
45
- Initialize the proxy registration manager.
46
-
47
- Args:
48
- config: Application configuration
49
- """
50
- self.config = config
51
- # Try both registration and proxy_registration for backward compatibility
52
- self.registration_config = config.get(
53
- "registration", config.get("proxy_registration", {})
54
- )
55
- # Auto-fill minimal TLS settings from global SSL if missing to support minimal config
56
- try:
57
- reg_ssl = self.registration_config.get("ssl", {})
58
- if not isinstance(reg_ssl, dict):
59
- reg_ssl = {}
60
- global_ssl = config.get("security", {}).get("ssl", {}) or config.get("ssl", {})
61
- if isinstance(global_ssl, dict):
62
- if not reg_ssl.get("ca_cert") and global_ssl.get("ca_cert"):
63
- reg_ssl["ca_cert"] = global_ssl.get("ca_cert")
64
- # Keep verify_mode if already set; default to CERT_REQUIRED if global verify_client true
65
- if not reg_ssl.get("verify_mode") and isinstance(global_ssl.get("verify_client"), bool):
66
- reg_ssl["verify_mode"] = "CERT_REQUIRED" if global_ssl.get("verify_client") else "CERT_NONE"
67
- if reg_ssl:
68
- self.registration_config["ssl"] = reg_ssl
69
- except Exception:
70
- pass
71
-
72
- # Check if registration is enabled
73
- self.enabled = self.registration_config.get("enabled", False)
74
-
75
- # Basic registration settings - only validate if enabled
76
- if self.enabled:
77
- self.proxy_url = self.registration_config.get("proxy_url")
78
- if not self.proxy_url:
79
- raise ValueError(
80
- "proxy_url is required in registration configuration. "
81
- "Please specify a valid proxy URL in your configuration."
82
- )
83
- else:
84
- self.proxy_url = None
85
-
86
- if self.enabled:
87
- self.server_id = self.registration_config.get("server_id")
88
- if not self.server_id:
89
- # Try to get from proxy_info.name as fallback
90
- self.server_id = self.registration_config.get("proxy_info", {}).get("name")
91
- if not self.server_id:
92
- raise ValueError(
93
- "server_id is required in registration configuration. "
94
- "Please specify a server_id or proxy_info.name in your configuration."
95
- )
96
- # UUID is mandatory for registration payload
97
- # Try to get UUID from proxy_registration section first, then from root config
98
- self.uuid = self.registration_config.get("uuid") or self.config.get("uuid")
99
- if not self.uuid:
100
- raise ValueError(
101
- "uuid is required in registration configuration. "
102
- "Please specify a UUID under 'proxy_registration.uuid' or at root level in your configuration."
103
- )
104
-
105
- # Validate UUID4 format
106
- import re
107
- uuid4_pattern = r'^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'
108
- if not re.match(uuid4_pattern, self.uuid, re.IGNORECASE):
109
- error_msg = f"Invalid UUID4 format: '{self.uuid}'. Expected format: xxxxxxxx-xxxx-4xxx-xxxx-xxxxxxxxxxxx"
110
- get_global_logger().error(f"❌ UUID validation failed: {error_msg}")
111
- raise ValueError(error_msg)
112
-
113
- get_global_logger().info(f"✅ UUID validation passed: {self.uuid}")
114
- self.server_name = self.registration_config.get("server_name")
115
- if not self.server_name:
116
- # Try to get from proxy_info.name as fallback
117
- self.server_name = self.registration_config.get("proxy_info", {}).get("name")
118
- if not self.server_name:
119
- raise ValueError(
120
- "server_name is required in registration configuration. "
121
- "Please specify a server_name or proxy_info.name in your configuration."
122
- )
123
- self.description = self.registration_config.get("description")
124
- if not self.description:
125
- # Try to get from proxy_info.description as fallback
126
- self.description = self.registration_config.get("proxy_info", {}).get("description")
127
- if not self.description:
128
- raise ValueError(
129
- "description is required in registration configuration. "
130
- "Please specify a description or proxy_info.description in your configuration."
131
- )
132
- else:
133
- self.server_id = None
134
- self.uuid = None
135
- self.server_name = None
136
- self.description = None
137
-
138
- if self.enabled:
139
- self.version = self.registration_config.get("version")
140
- if not self.version:
141
- # Try to get from proxy_info.version as fallback
142
- self.version = self.registration_config.get("proxy_info", {}).get("version")
143
- if not self.version:
144
- raise ValueError(
145
- "version is required in registration configuration. "
146
- "Please specify a version or proxy_info.version in your configuration."
147
- )
148
- else:
149
- self.version = None
150
-
151
- # Heartbeat settings - only validate if enabled
152
- if self.enabled:
153
- heartbeat_config = self.registration_config.get("heartbeat", {})
154
- heartbeat_enabled = heartbeat_config.get("enabled", True)
155
-
156
- if heartbeat_enabled:
157
- self.timeout = heartbeat_config.get("timeout")
158
- if self.timeout is None:
159
- raise ValueError(
160
- "heartbeat.timeout is required in registration configuration. "
161
- "Please specify a timeout value."
162
- )
163
- self.retry_attempts = heartbeat_config.get("retry_attempts")
164
- if self.retry_attempts is None:
165
- raise ValueError(
166
- "heartbeat.retry_attempts is required in registration configuration. "
167
- "Please specify a retry_attempts value."
168
- )
169
- self.retry_delay = heartbeat_config.get("retry_delay")
170
- if self.retry_delay is None:
171
- raise ValueError(
172
- "heartbeat.retry_delay is required in registration configuration. "
173
- "Please specify a retry_delay value."
174
- )
175
- self.heartbeat_interval = heartbeat_config.get("interval")
176
- if self.heartbeat_interval is None:
177
- raise ValueError(
178
- "heartbeat.interval is required in registration configuration. "
179
- "Please specify an interval value."
180
- )
181
- else:
182
- # Heartbeat disabled - use defaults
183
- self.timeout = heartbeat_config.get("timeout", 30)
184
- self.retry_attempts = heartbeat_config.get("retry_attempts", 3)
185
- self.retry_delay = heartbeat_config.get("retry_delay", 5)
186
- self.heartbeat_interval = heartbeat_config.get("interval", 30)
187
- else:
188
- self.timeout = None
189
- self.retry_attempts = None
190
- self.retry_delay = None
191
- self.heartbeat_interval = None
192
-
193
- # Auto registration settings
194
- if self.enabled:
195
- self.auto_register = self.registration_config.get("enabled")
196
- if self.auto_register is None:
197
- raise ValueError(
198
- "enabled is required in registration configuration. "
199
- "Please specify whether registration is enabled (true/false)."
200
- )
201
- else:
202
- self.auto_register = False
203
-
204
- self.auto_unregister = True # Always unregister on shutdown
205
-
206
- # Initialize client security manager
207
- self.client_security = create_client_security_manager(config)
208
-
209
- # Registration state
210
- self.registered = False
211
- self.server_key: Optional[str] = None
212
- self.server_url: Optional[str] = None
213
- self.heartbeat_task: Optional[asyncio.Task] = None
214
-
215
- get_global_logger().info(
216
- "Proxy registration manager initialized with security framework integration"
217
- )
218
-
219
- def is_enabled(self) -> bool:
220
- """
221
- Check if proxy registration is enabled.
222
-
223
- Returns:
224
- True if registration is enabled, False otherwise.
225
- """
226
- return self.enabled
227
-
228
- def set_server_url(self, server_url: str) -> None:
229
- """
230
- Set the server URL for registration.
231
-
232
- Args:
233
- server_url: The URL where this server is accessible.
234
- """
235
- self.server_url = server_url
236
- get_global_logger().info(f"Proxy registration server URL set to: {server_url}")
237
-
238
- def _get_auth_headers(self) -> Dict[str, str]:
239
- """
240
- Get authentication headers for registration requests.
241
-
242
- Returns:
243
- Dictionary of authentication headers
244
- """
245
- if not self.client_security:
246
- return {"Content-Type": "application/json"}
247
-
248
- auth_method = self.registration_config.get("auth_method", "certificate")
249
-
250
- if auth_method == "certificate":
251
- return self.client_security.get_client_auth_headers("certificate")
252
- elif auth_method == "token":
253
- token_config = self.registration_config.get("token", {})
254
- token = token_config.get("token")
255
- return self.client_security.get_client_auth_headers("jwt", token=token)
256
- elif auth_method == "api_key":
257
- api_key_config = self.registration_config.get("api_key", {})
258
- api_key = api_key_config.get("key")
259
- return self.client_security.get_client_auth_headers(
260
- "api_key", api_key=api_key
261
- )
262
- else:
263
- return {"Content-Type": "application/json"}
264
-
265
- def _create_ssl_context(self) -> Optional[ssl.SSLContext]:
266
- """
267
- Create SSL context for secure connections using registration SSL configuration.
268
-
269
- Returns:
270
- SSL context or None if SSL not needed
271
- """
272
- get_global_logger().debug("_create_ssl_context called")
273
-
274
- # Decide SSL strictly by proxy URL scheme: use SSL only for https proxy URLs
275
- try:
276
- from urllib.parse import urlparse as _urlparse
277
- scheme = _urlparse(self.proxy_url).scheme if self.proxy_url else "http"
278
- if scheme.lower() != "https":
279
- get_global_logger().debug("Proxy URL is HTTP, skipping SSL context creation for registration")
280
- return None
281
- except Exception:
282
- get_global_logger().debug("Failed to parse proxy_url, assuming HTTP and skipping SSL context")
283
- return None
284
-
285
- if not self.client_security:
286
- get_global_logger().debug("SSL context creation failed: client_security is None")
287
- return None
288
-
289
- try:
290
- # Check if SSL is enabled for registration
291
- cert_config = self.registration_config.get("certificate", {})
292
- ssl_config = self.registration_config.get("ssl", {})
293
-
294
- # FALLBACK: if no explicit registration SSL/certs provided, reuse global SSL config
295
- if not cert_config and not ssl_config:
296
- global_ssl = self.config.get("security", {}).get("ssl", {}) or self.config.get("ssl", {})
297
- if global_ssl:
298
- # Map global ssl to registration-style configs
299
- mapped_cert = {}
300
- if global_ssl.get("cert_file") and global_ssl.get("key_file"):
301
- mapped_cert = {
302
- "cert_file": global_ssl.get("cert_file"),
303
- "key_file": global_ssl.get("key_file"),
304
- }
305
- mapped_ssl = {}
306
- if global_ssl.get("ca_cert"):
307
- mapped_ssl["ca_cert"] = global_ssl.get("ca_cert")
308
- if global_ssl.get("verify_client") is not None:
309
- mapped_ssl["verify_mode"] = (
310
- "CERT_REQUIRED" if global_ssl.get("verify_client") else "CERT_NONE"
311
- )
312
- cert_config = mapped_cert
313
- ssl_config = mapped_ssl
314
-
315
- # If still no client certificate specified, raise clear error
316
- if not cert_config or not cert_config.get("cert_file") or not cert_config.get("key_file"):
317
- raise ValueError(
318
- "Client certificate configuration is required for mTLS proxy registration. "
319
- "Please configure 'proxy_registration.certificate.cert_file' and 'proxy_registration.certificate.key_file' "
320
- "in your configuration file."
321
- )
322
-
323
- get_global_logger().debug(
324
- f"SSL context creation: cert_config={cert_config}, ssl_config={ssl_config}"
325
- )
326
-
327
- # SSL is enabled if certificate config exists or SSL config exists
328
- if cert_config or ssl_config:
329
- # Create a custom SSL context based on registration configuration
330
- ca_file = ssl_config.get("ca_cert") if isinstance(ssl_config, dict) else None
331
- if ca_file and Path(ca_file).exists():
332
- context = ssl.create_default_context(cafile=ca_file)
333
- else:
334
- # No CA certificate configured - use system default
335
- context = ssl.create_default_context()
336
- get_global_logger().warning(
337
- "No CA certificate configured for proxy registration. "
338
- "This may cause SSL verification failures if the proxy uses self-signed certificates. "
339
- "Consider configuring 'proxy_registration.ssl.ca_cert' in your configuration file."
340
- )
341
-
342
- # Load client certificates if provided
343
- if cert_config:
344
- cert_file = cert_config.get("cert_file")
345
- key_file = cert_config.get("key_file")
346
-
347
- if cert_file and key_file:
348
- context.load_cert_chain(cert_file, key_file)
349
- get_global_logger().debug(
350
- f"Loaded client certificates for mTLS: cert={cert_file}, key={key_file}"
351
- )
352
-
353
- # Configure SSL verification based on registration settings
354
- if ssl_config:
355
- ca_cert_file = ssl_config.get("ca_cert")
356
- verify_mode = ssl_config.get("verify_mode", "CERT_REQUIRED")
357
- verify_ssl = ssl_config.get("verify_ssl", True)
358
-
359
- # Load CA certificate if provided
360
- if ca_cert_file:
361
- context.load_verify_locations(ca_cert_file)
362
- get_global_logger().debug(f"Loaded CA certificate: {ca_cert_file}")
363
-
364
- # Check if verify_ssl is disabled in ssl_config
365
- if verify_ssl == False:
366
- context.check_hostname = False
367
- context.verify_mode = ssl.CERT_NONE
368
- get_global_logger().debug("SSL verification disabled (verify_ssl=False)")
369
- elif verify_mode == "CERT_NONE":
370
- context.check_hostname = False
371
- context.verify_mode = ssl.CERT_NONE
372
- get_global_logger().debug("SSL verification disabled (CERT_NONE)")
373
- elif verify_mode == "CERT_REQUIRED":
374
- context.check_hostname = True
375
- context.verify_mode = ssl.CERT_REQUIRED
376
- get_global_logger().debug("SSL verification enabled (CERT_REQUIRED)")
377
- else:
378
- # For test environments, default to CERT_NONE to avoid certificate issues
379
- context.check_hostname = False
380
- context.verify_mode = ssl.CERT_NONE
381
- get_global_logger().debug("SSL verification disabled (default for test environment)")
382
- else:
383
- # No specific ssl_config, default to CERT_NONE for test environments
384
- context.check_hostname = False
385
- context.verify_mode = ssl.CERT_NONE
386
- get_global_logger().debug("Using CERT_NONE for test environment (no ssl_config)")
387
-
388
- get_global_logger().info("Created custom SSL context for proxy registration")
389
- return context
390
- else:
391
- get_global_logger().debug(
392
- "SSL context creation skipped: no cert_config or ssl_config"
393
- )
394
-
395
- return None
396
- except Exception as e:
397
- get_global_logger().warning(f"Failed to create SSL context: {e}")
398
- # Don't fail the entire operation, just return None
399
- return None
400
-
401
- async def register_server(self) -> bool:
402
- """
403
- Register the server with the proxy using secure authentication.
404
-
405
- Returns:
406
- True if registration was successful, False otherwise.
407
- """
408
- if not self.is_enabled():
409
- get_global_logger().info("Proxy registration is disabled in configuration")
410
- return False
411
-
412
- if not self.server_url:
413
- get_global_logger().error("Server URL not set, cannot register with proxy")
414
- return False
415
-
416
- # Normalize server_url for docker host if needed
417
- try:
418
- if self.server_url:
419
- from urllib.parse import urlparse, urlunparse
420
- import os as _os
421
- parsed = urlparse(self.server_url)
422
- if parsed.hostname in ("localhost", "127.0.0.1"):
423
- docker_addr = _os.getenv("DOCKER_HOST_ADDR", "172.17.0.1")
424
- port = parsed.port
425
- if not port:
426
- port = 443 if parsed.scheme == "https" else 80
427
- new_netloc = f"{docker_addr}:{port}"
428
- normalized = urlunparse(parsed._replace(netloc=new_netloc))
429
- if normalized != self.server_url:
430
- self.server_url = normalized
431
- get_global_logger().info(
432
- f"Normalized server_url for docker host: {self.server_url}"
433
- )
434
- except Exception as _e:
435
- get_global_logger().debug(f"server_url normalization skipped: {_e}")
436
-
437
- # Prepare registration data with proxy info
438
- proxy_info = self.registration_config.get("proxy_info", {})
439
- registration_data = {
440
- "server_id": self.server_id,
441
- "uuid": self.uuid,
442
- "server_url": self.server_url,
443
- "server_name": self.server_name,
444
- "description": self.description,
445
- "version": self.version,
446
- "capabilities": proxy_info.get("capabilities", ["jsonrpc", "rest"]),
447
- "endpoints": proxy_info.get(
448
- "endpoints",
449
- {"jsonrpc": "/api/jsonrpc", "rest": "/cmd", "health": "/health"},
450
- ),
451
- }
452
-
453
- get_global_logger().info(f"Attempting to register server with proxy at {self.proxy_url}")
454
- get_global_logger().debug(f"Registration data: {registration_data}")
455
-
456
- # Do not block application startup: single attempt, no sleeps here
457
- for attempt in range(1):
458
- try:
459
- success, result = await self._make_secure_registration_request(
460
- registration_data
461
- )
462
-
463
- if success:
464
- self.registered = True
465
- # Safely extract server_key from result
466
- if isinstance(result, dict):
467
- self.server_key = result.get("server_key")
468
- else:
469
- self.server_key = None
470
- get_global_logger().info(
471
- f"✅ Successfully registered with proxy. Server key: {self.server_key}"
472
- )
473
-
474
- # Start heartbeat if enabled
475
- if self.registration_config.get("heartbeat", {}).get(
476
- "enabled", True
477
- ):
478
- await self._start_heartbeat()
479
-
480
- return True
481
- else:
482
- # Be robust if result is not a dict
483
- error_msg = None
484
- get_global_logger().error(f"DEBUG: result type = {type(result)}, result = {result}")
485
- if isinstance(result, dict):
486
- get_global_logger().error(f"DEBUG: result is dict, getting error field")
487
- error_field = result.get("error", {})
488
- get_global_logger().error(f"DEBUG: error_field type = {type(error_field)}, error_field = {error_field}")
489
- if isinstance(error_field, dict):
490
- error_msg = error_field.get("message", "Unknown error")
491
- elif isinstance(error_field, str):
492
- error_msg = error_field
493
- else:
494
- error_msg = str(error_field)
495
-
496
- # Auto-recovery: already registered case → force unregistration then retry once
497
- error_code = result.get("error_code") or (result.get("error", {}).get("code") if isinstance(result.get("error"), dict) else None)
498
- already_registered = False
499
- existing_server_key = None
500
- # Prefer structured detail if provided
501
- if isinstance(result.get("details"), dict):
502
- existing_server_key = result.get("details", {}).get("existing_server_key")
503
- # Fallback: parse from error message text
504
- if not existing_server_key and isinstance(error_msg, str) and "already registered as" in error_msg:
505
- try:
506
- # Expecting: "... already registered as <server_id>_<copy_number>"
507
- tail = error_msg.split("already registered as", 1)[1].strip()
508
- existing_server_key = tail.split()[0]
509
- except Exception:
510
- existing_server_key = None
511
-
512
- if (error_code in ("DUPLICATE_SERVER_URL", "REGISTRATION_ERROR") or already_registered) and existing_server_key:
513
- try:
514
- get_global_logger().info(f"Attempting auto-unregistration of existing instance: {existing_server_key}")
515
- # Build unregistration payload using parsed server_key
516
- try:
517
- copy_number = int(existing_server_key.split("_")[-1])
518
- except Exception:
519
- copy_number = 1
520
- unregistration_data = {"server_id": self.server_id, "copy_number": copy_number}
521
- # Reuse secure unregistration request directly
522
- unreg_success, _unreg_result = await self._make_secure_unregistration_request(unregistration_data)
523
- if unreg_success:
524
- get_global_logger().info("Auto-unregistration succeeded, retrying registration once...")
525
- # Retry registration once immediately
526
- retry_success, retry_result = await self._make_secure_registration_request(registration_data)
527
- if retry_success:
528
- self.registered = True
529
- if isinstance(retry_result, dict):
530
- self.server_key = retry_result.get("server_key")
531
- else:
532
- self.server_key = None
533
- get_global_logger().info(f"✅ Successfully registered after auto-unregistration. Server key: {self.server_key}")
534
- if self.registration_config.get("heartbeat", {}).get("enabled", True):
535
- await self._start_heartbeat()
536
- return True
537
- else:
538
- get_global_logger().warning(f"Retry registration failed after auto-unregistration: {retry_result}")
539
- else:
540
- get_global_logger().warning(f"Auto-unregistration failed: {_unreg_result}")
541
- except Exception as _auto_e:
542
- get_global_logger().warning(f"Auto-unregistration/registration flow error: {_auto_e}")
543
- else:
544
- error_msg = str(result)
545
- get_global_logger().warning(
546
- f"❌ Registration attempt {attempt + 1} failed: {error_msg}"
547
- )
548
-
549
- except Exception as e:
550
- get_global_logger().error(
551
- f"❌ Registration attempt {attempt + 1} failed with exception: {e}"
552
- )
553
- get_global_logger().error(f"Full traceback: {traceback.format_exc()}")
554
-
555
- get_global_logger().error(
556
- f"❌ Failed to register with proxy after {self.retry_attempts} attempts"
557
- )
558
- return False
559
-
560
- async def unregister_server(self) -> bool:
561
- """
562
- Unregister the server from the proxy.
563
-
564
- Returns:
565
- True if unregistration was successful, False otherwise.
566
- """
567
- if not self.is_enabled():
568
- get_global_logger().info("Proxy registration is disabled, skipping unregistration")
569
- return True
570
-
571
- if not self.registered or not self.server_key:
572
- get_global_logger().info("Server not registered with proxy, skipping unregistration")
573
- return True
574
-
575
- # Stop heartbeat
576
- await self._stop_heartbeat()
577
-
578
- # Extract copy_number from server_key (format: server_id_copy_number)
579
- try:
580
- copy_number = int(self.server_key.split("_")[-1])
581
- except (ValueError, IndexError):
582
- copy_number = 1
583
-
584
- unregistration_data = {"server_id": self.server_id, "copy_number": copy_number}
585
-
586
- get_global_logger().info(f"Attempting to unregister server from proxy at {self.proxy_url}")
587
- get_global_logger().debug(f"Unregistration data: {unregistration_data}")
588
-
589
- try:
590
- success, result = await self._make_secure_unregistration_request(
591
- unregistration_data
592
- )
593
-
594
- if success:
595
- unregistered = result.get("unregistered", False)
596
- if unregistered:
597
- get_global_logger().info("✅ Successfully unregistered from proxy")
598
- else:
599
- get_global_logger().warning("⚠️ Server was not found in proxy registry")
600
-
601
- self.registered = False
602
- self.server_key = None
603
- return True
604
- else:
605
- error_msg = result.get("error", {}).get("message", "Unknown error")
606
- get_global_logger().error(f"❌ Failed to unregister from proxy: {error_msg}")
607
- return False
608
-
609
- except Exception as e:
610
- get_global_logger().error(f"❌ Unregistration failed with exception: {e}")
611
- return False
612
-
613
- async def _make_secure_registration_request(
614
- self, data: Dict[str, Any]
615
- ) -> Tuple[bool, Dict[str, Any]]:
616
- """
617
- Make secure registration request to proxy using security framework.
618
-
619
- Args:
620
- data: Registration data.
621
-
622
- Returns:
623
- Tuple of (success, result).
624
- """
625
- url = urljoin(self.proxy_url, "/register")
626
-
627
- # Get authentication headers
628
- headers = self._get_auth_headers()
629
- headers["Content-Type"] = "application/json"
630
-
631
- # Create SSL context if needed
632
- ssl_context = self._create_ssl_context()
633
-
634
- # Create connector with SSL context
635
- connector = None
636
- if ssl_context:
637
- connector = aiohttp.TCPConnector(ssl=ssl_context)
638
-
639
- try:
640
- async with aiohttp.ClientSession(connector=connector) as session:
641
- async with session.post(
642
- url,
643
- json=data,
644
- headers=headers,
645
- timeout=aiohttp.ClientTimeout(total=self.timeout),
646
- ) as response:
647
- try:
648
- result = await response.json()
649
- get_global_logger().debug(f"Response JSON parsed successfully: {type(result)} - {result}")
650
- except Exception as e:
651
- text_body = await response.text()
652
- get_global_logger().debug(f"JSON parsing failed: {e}, text_body: {text_body}")
653
- result = {"success": False, "error": {"code": "NON_JSON_RESPONSE", "message": text_body}}
654
-
655
- # Validate response headers if security framework available
656
- if self.client_security:
657
- self.client_security.validate_server_response(
658
- dict(response.headers)
659
- )
660
-
661
- # Check both HTTP status and JSON success field
662
- if response.status == 200:
663
- success = result.get("success", False)
664
- if not success:
665
- error_info = result.get("error", {})
666
- error_msg = error_info.get("message", "Unknown error")
667
- error_code = error_info.get("code", "UNKNOWN_ERROR")
668
-
669
- # Handle duplicate server URL as successful registration
670
- if error_code == "DUPLICATE_SERVER_URL":
671
- get_global_logger().info(
672
- f"✅ Server already registered: {error_msg}"
673
- )
674
- # Extract server_key from details if available
675
- details = error_info.get("details", {})
676
- existing_server_key = details.get("existing_server_key")
677
- if existing_server_key:
678
- result["server_key"] = existing_server_key
679
- get_global_logger().info(
680
- f"✅ Retrieved existing server key: {existing_server_key}"
681
- )
682
- # Return success=True for duplicate registration
683
- return True, result
684
- else:
685
- get_global_logger().warning(
686
- f"Registration failed: {error_code} - {error_msg}"
687
- )
688
- return success, result
689
- else:
690
- get_global_logger().warning(
691
- f"Registration failed with HTTP status: {response.status}"
692
- )
693
- # Ensure result is a dict for consistent error handling
694
- if isinstance(result, str):
695
- result = {"success": False, "error": {"code": "HTTP_ERROR", "message": result}}
696
- return False, result
697
- finally:
698
- if connector:
699
- await connector.close()
700
-
701
- async def _make_secure_unregistration_request(
702
- self, data: Dict[str, Any]
703
- ) -> Tuple[bool, Dict[str, Any]]:
704
- """
705
- Make secure unregistration request to proxy using security framework.
706
-
707
- Args:
708
- data: Unregistration data.
709
-
710
- Returns:
711
- Tuple of (success, result).
712
- """
713
- url = urljoin(self.proxy_url, "/unregister")
714
-
715
- # Get authentication headers
716
- headers = self._get_auth_headers()
717
- headers["Content-Type"] = "application/json"
718
-
719
- # Create SSL context if needed
720
- ssl_context = self._create_ssl_context()
721
-
722
- # Create connector with SSL context
723
- connector = None
724
- if ssl_context:
725
- connector = aiohttp.TCPConnector(ssl=ssl_context)
726
-
727
- try:
728
- async with aiohttp.ClientSession(connector=connector) as session:
729
- async with session.post(
730
- url,
731
- json=data,
732
- headers=headers,
733
- timeout=aiohttp.ClientTimeout(total=self.timeout),
734
- ) as response:
735
- result = await response.json()
736
-
737
- # Validate response headers if security framework available
738
- if self.client_security:
739
- self.client_security.validate_server_response(
740
- dict(response.headers)
741
- )
742
-
743
- # Check both HTTP status and JSON success field
744
- if response.status == 200:
745
- success = result.get("success", False)
746
- if not success:
747
- error_info = result.get("error", {})
748
- error_msg = error_info.get("message", "Unknown error")
749
- error_code = error_info.get("code", "UNKNOWN_ERROR")
750
- get_global_logger().warning(
751
- f"Unregistration failed: {error_code} - {error_msg}"
752
- )
753
- return success, result
754
- else:
755
- get_global_logger().warning(
756
- f"Unregistration failed with HTTP status: {response.status}"
757
- )
758
- return False, result
759
- finally:
760
- if connector:
761
- await connector.close()
762
-
763
- async def _start_heartbeat(self) -> None:
764
- """Start heartbeat task for keeping registration alive."""
765
- if self.heartbeat_task and not self.heartbeat_task.done():
766
- return
767
-
768
- self.heartbeat_task = asyncio.create_task(self._heartbeat_loop())
769
- get_global_logger().info("Heartbeat task started")
770
-
771
- async def _stop_heartbeat(self) -> None:
772
- """Stop heartbeat task."""
773
- if self.heartbeat_task and not self.heartbeat_task.done():
774
- self.heartbeat_task.cancel()
775
- try:
776
- await self.heartbeat_task
777
- except asyncio.CancelledError:
778
- pass
779
- get_global_logger().info("Heartbeat task stopped")
780
-
781
- async def _heartbeat_loop(self) -> None:
782
- """Heartbeat loop to keep registration alive."""
783
- while self.registered:
784
- try:
785
- await asyncio.sleep(self.heartbeat_interval)
786
-
787
- if not self.registered:
788
- break
789
-
790
- # Send heartbeat
791
- success = await self._send_heartbeat()
792
- if not success:
793
- get_global_logger().warning("Heartbeat failed, attempting to re-register")
794
- await self.register_server()
795
-
796
- except asyncio.CancelledError:
797
- break
798
- except Exception as e:
799
- get_global_logger().error(f"Heartbeat error: {e}")
800
-
801
- async def heartbeat(self) -> bool:
802
- """
803
- Public method to send heartbeat to proxy server.
804
-
805
- Returns:
806
- True if heartbeat was successful, False otherwise.
807
- """
808
- return await self._send_heartbeat()
809
-
810
- async def _send_heartbeat(self) -> bool:
811
- """Send heartbeat to proxy server."""
812
- if not self.server_key:
813
- return False
814
-
815
- url = urljoin(self.proxy_url, "/heartbeat")
816
-
817
- # Get authentication headers
818
- headers = self._get_auth_headers()
819
-
820
- # Create SSL context if needed
821
- ssl_context = self._create_ssl_context()
822
-
823
- # Create connector with SSL context
824
- connector = aiohttp.TCPConnector(ssl=ssl_context) if ssl_context else None
825
-
826
- try:
827
- timeout = aiohttp.ClientTimeout(total=self.timeout)
828
- async with aiohttp.ClientSession(connector=connector) as session:
829
- # Prefer GET heartbeat (container exposes GET /heartbeat)
830
- try:
831
- async with session.get(url, headers=headers, timeout=timeout) as resp:
832
- if resp.status == 200:
833
- get_global_logger().debug("Heartbeat (GET) succeeded")
834
- return True
835
- # If method not allowed, fall back to POST
836
- if resp.status != 405:
837
- get_global_logger().warning(
838
- f"Heartbeat (GET) failed with status: {resp.status}"
839
- )
840
- except Exception as ge:
841
- get_global_logger().debug(f"Heartbeat (GET) error: {ge}")
842
-
843
- # Fallback to POST if GET not supported
844
- heartbeat_data = {
845
- "server_id": self.server_id,
846
- "server_key": self.server_key,
847
- "timestamp": int(time.time()),
848
- }
849
- post_headers = dict(headers)
850
- post_headers["Content-Type"] = "application/json"
851
- async with session.post(
852
- url, json=heartbeat_data, headers=post_headers, timeout=timeout
853
- ) as resp:
854
- if resp.status == 200:
855
- get_global_logger().debug("Heartbeat (POST) succeeded")
856
- return True
857
- get_global_logger().warning(
858
- f"Heartbeat (POST) failed with status: {resp.status}"
859
- )
860
- return False
861
- except Exception as e:
862
- get_global_logger().error(f"Heartbeat error: {e}")
863
- return False
864
- finally:
865
- if connector:
866
- await connector.close()
867
-
868
- def get_registration_status(self) -> Dict[str, Any]:
869
- """
870
- Get current registration status.
871
-
872
- Returns:
873
- Dictionary with registration status information.
874
- """
875
- status = {
876
- "enabled": self.is_enabled(),
877
- "registered": self.registered,
878
- "server_key": self.server_key,
879
- "server_url": self.server_url,
880
- "proxy_url": self.proxy_url,
881
- "server_id": self.server_id,
882
- "heartbeat_active": self.heartbeat_task is not None
883
- and not self.heartbeat_task.done(),
884
- }
885
-
886
- # Add security information if available
887
- if self.client_security:
888
- status["security_enabled"] = True
889
- status["ssl_enabled"] = self.client_security.is_ssl_enabled()
890
- status["auth_methods"] = self.client_security.get_supported_auth_methods()
891
-
892
- cert_info = self.client_security.get_client_certificate_info()
893
- if cert_info:
894
- status["client_certificate"] = cert_info
895
- else:
896
- status["security_enabled"] = False
897
-
898
- return status
899
-
900
-
901
- # Global proxy registration manager instance (will be initialized with config)
902
- proxy_registration_manager: Optional[ProxyRegistrationManager] = None
20
+ # Global registration manager instance
21
+ _registration_manager: Optional[ProxyRegistrationManager] = None
903
22
 
904
23
 
905
24
  def initialize_proxy_registration(config: Dict[str, Any]) -> None:
906
25
  """
907
- Initialize global proxy registration manager.
26
+ Initialize proxy registration with configuration.
908
27
 
909
28
  Args:
910
29
  config: Application configuration
911
30
  """
912
- global proxy_registration_manager
913
- proxy_registration_manager = ProxyRegistrationManager(config)
31
+ global _registration_manager
32
+ _registration_manager = ProxyRegistrationManager(config)
914
33
 
915
34
 
916
35
  async def register_with_proxy(server_url: str) -> bool:
917
36
  """
918
- Register the server with the proxy.
37
+ Register server with proxy.
919
38
 
920
39
  Args:
921
- server_url: The URL where this server is accessible.
40
+ server_url: Server URL to register
922
41
 
923
42
  Returns:
924
- True if registration was successful, False otherwise.
43
+ True if registration successful, False otherwise
925
44
  """
926
- if not proxy_registration_manager:
927
- get_global_logger().error("Proxy registration manager not initialized")
45
+ if not _registration_manager:
928
46
  return False
929
-
930
- proxy_registration_manager.set_server_url(server_url)
931
- return await proxy_registration_manager.register_server()
47
+
48
+ _registration_manager.set_server_url(server_url)
49
+ return await _registration_manager.register()
932
50
 
933
51
 
934
52
  async def unregister_from_proxy() -> bool:
935
53
  """
936
- Unregister the server from the proxy.
54
+ Unregister server from proxy.
937
55
 
938
56
  Returns:
939
- True if unregistration was successful, False otherwise.
57
+ True if unregistration successful, False otherwise
940
58
  """
941
- if not proxy_registration_manager:
942
- get_global_logger().error("Proxy registration manager not initialized")
943
- return False
944
-
945
- return await proxy_registration_manager.unregister_server()
59
+ if not _registration_manager:
60
+ return True
61
+
62
+ return await _registration_manager.unregister()
946
63
 
947
64
 
948
65
  def get_proxy_registration_status() -> Dict[str, Any]:
@@ -950,9 +67,16 @@ def get_proxy_registration_status() -> Dict[str, Any]:
950
67
  Get current proxy registration status.
951
68
 
952
69
  Returns:
953
- Dictionary with registration status information.
70
+ Dictionary with registration status information
954
71
  """
955
- if not proxy_registration_manager:
956
- return {"error": "Proxy registration manager not initialized"}
957
-
958
- return proxy_registration_manager.get_registration_status()
72
+ if not _registration_manager:
73
+ return {
74
+ "enabled": False,
75
+ "registered": False,
76
+ "proxy_url": None,
77
+ "server_url": None,
78
+ "registration_time": None,
79
+ "client_security_available": False,
80
+ }
81
+
82
+ return _registration_manager.get_registration_status()