mcp-proxy-adapter 6.9.28__py3-none-any.whl → 6.9.30__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 +10 -9
  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 -913
  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.28.dist-info → mcp_proxy_adapter-6.9.30.dist-info}/METADATA +2 -1
  208. mcp_proxy_adapter-6.9.30.dist-info/RECORD +235 -0
  209. {mcp_proxy_adapter-6.9.28.dist-info → mcp_proxy_adapter-6.9.30.dist-info}/entry_points.txt +1 -1
  210. mcp_proxy_adapter-6.9.28.dist-info/RECORD +0 -149
  211. {mcp_proxy_adapter-6.9.28.dist-info → mcp_proxy_adapter-6.9.30.dist-info}/WHEEL +0 -0
  212. {mcp_proxy_adapter-6.9.28.dist-info → mcp_proxy_adapter-6.9.30.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,940 +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
- verify_hostname = ssl_config.get("verify_hostname", True) # ✅ Read verify_hostname setting
359
-
360
- # Load CA certificate if provided
361
- if ca_cert_file:
362
- context.load_verify_locations(ca_cert_file)
363
- get_global_logger().debug(f"Loaded CA certificate: {ca_cert_file}")
364
-
365
- # Check if verify_ssl is disabled in ssl_config
366
- if verify_ssl == False:
367
- context.check_hostname = False
368
- context.verify_mode = ssl.CERT_NONE
369
- get_global_logger().debug("SSL verification disabled (verify_ssl=False)")
370
- elif verify_mode == "CERT_NONE":
371
- context.check_hostname = False
372
- context.verify_mode = ssl.CERT_NONE
373
- get_global_logger().debug("SSL verification disabled (CERT_NONE)")
374
- elif verify_mode == "CERT_REQUIRED":
375
- context.check_hostname = verify_hostname # ✅ Use verify_hostname setting
376
- context.verify_mode = ssl.CERT_REQUIRED
377
- get_global_logger().debug(f"SSL verification enabled (CERT_REQUIRED), hostname check: {verify_hostname}")
378
- else:
379
- # For test environments, default to CERT_NONE to avoid certificate issues
380
- context.check_hostname = False
381
- context.verify_mode = ssl.CERT_NONE
382
- get_global_logger().debug("SSL verification disabled (default for test environment)")
383
- else:
384
- # No specific ssl_config, default to CERT_NONE for test environments
385
- context.check_hostname = False
386
- context.verify_mode = ssl.CERT_NONE
387
- get_global_logger().debug("Using CERT_NONE for test environment (no ssl_config)")
388
-
389
- get_global_logger().info("Created custom SSL context for proxy registration")
390
- return context
391
- else:
392
- get_global_logger().debug(
393
- "SSL context creation skipped: no cert_config or ssl_config"
394
- )
395
-
396
- return None
397
- except Exception as e:
398
- get_global_logger().warning(f"Failed to create SSL context: {e}")
399
- # Don't fail the entire operation, just return None
400
- return None
401
-
402
- async def register_server(self) -> bool:
403
- """
404
- Register the server with the proxy using secure authentication.
405
-
406
- Returns:
407
- True if registration was successful, False otherwise.
408
- """
409
- if not self.is_enabled():
410
- get_global_logger().info("Proxy registration is disabled in configuration")
411
- return False
412
-
413
- if not self.server_url:
414
- get_global_logger().error("Server URL not set, cannot register with proxy")
415
- return False
416
-
417
- # Normalize server_url for docker host if needed
418
- try:
419
- if self.server_url:
420
- from urllib.parse import urlparse, urlunparse
421
- import os as _os
422
- parsed = urlparse(self.server_url)
423
- if parsed.hostname in ("localhost", "127.0.0.1"):
424
- docker_addr = _os.getenv("DOCKER_HOST_ADDR", "172.17.0.1")
425
- port = parsed.port
426
- if not port:
427
- port = 443 if parsed.scheme == "https" else 80
428
- new_netloc = f"{docker_addr}:{port}"
429
- normalized = urlunparse(parsed._replace(netloc=new_netloc))
430
- if normalized != self.server_url:
431
- self.server_url = normalized
432
- get_global_logger().info(
433
- f"Normalized server_url for docker host: {self.server_url}"
434
- )
435
- except Exception as _e:
436
- get_global_logger().debug(f"server_url normalization skipped: {_e}")
437
-
438
- # Prepare registration data with proxy info
439
- proxy_info = self.registration_config.get("proxy_info", {})
440
- registration_data = {
441
- "server_id": self.server_id,
442
- "uuid": self.uuid,
443
- "server_url": self.server_url,
444
- "server_name": self.server_name,
445
- "description": self.description,
446
- "version": self.version,
447
- "capabilities": proxy_info.get("capabilities", ["jsonrpc", "rest"]),
448
- "endpoints": proxy_info.get(
449
- "endpoints",
450
- {"jsonrpc": "/api/jsonrpc", "rest": "/cmd", "health": "/health"},
451
- ),
452
- }
453
-
454
- get_global_logger().info(f"Attempting to register server with proxy at {self.proxy_url}")
455
- get_global_logger().debug(f"Registration data: {registration_data}")
456
-
457
- # Do not block application startup: single attempt, no sleeps here
458
- for attempt in range(1):
459
- try:
460
- success, result = await self._make_secure_registration_request(
461
- registration_data
462
- )
463
-
464
- if success:
465
- self.registered = True
466
- # Safely extract server_key from result
467
- if isinstance(result, dict):
468
- self.server_key = result.get("server_key")
469
- else:
470
- self.server_key = None
471
- get_global_logger().info(
472
- f"✅ Successfully registered with proxy. Server key: {self.server_key}"
473
- )
474
-
475
- # Start heartbeat if enabled
476
- if self.registration_config.get("heartbeat", {}).get(
477
- "enabled", True
478
- ):
479
- await self._start_heartbeat()
480
-
481
- return True
482
- else:
483
- # Be robust if result is not a dict
484
- error_msg = None
485
- get_global_logger().error(f"DEBUG: result type = {type(result)}, result = {result}")
486
- if isinstance(result, dict):
487
- get_global_logger().error(f"DEBUG: result is dict, getting error field")
488
- error_field = result.get("error", {})
489
- get_global_logger().error(f"DEBUG: error_field type = {type(error_field)}, error_field = {error_field}")
490
- if isinstance(error_field, dict):
491
- error_msg = error_field.get("message", "Unknown error")
492
- elif isinstance(error_field, str):
493
- error_msg = error_field
494
- else:
495
- error_msg = str(error_field)
496
-
497
- # Auto-recovery: already registered case → force unregistration then retry once
498
- error_code = result.get("error_code") or (result.get("error", {}).get("code") if isinstance(result.get("error"), dict) else None)
499
- already_registered = False
500
- existing_server_key = None
501
- # Prefer structured detail if provided
502
- if isinstance(result.get("details"), dict):
503
- existing_server_key = result.get("details", {}).get("existing_server_key")
504
- # Fallback: parse from error message text
505
- if not existing_server_key and isinstance(error_msg, str) and "already registered as" in error_msg:
506
- try:
507
- # Expecting: "... already registered as <server_id>_<copy_number>"
508
- tail = error_msg.split("already registered as", 1)[1].strip()
509
- existing_server_key = tail.split()[0]
510
- except Exception:
511
- existing_server_key = None
512
-
513
- if (error_code in ("DUPLICATE_SERVER_URL", "REGISTRATION_ERROR") or already_registered) and existing_server_key:
514
- try:
515
- get_global_logger().info(f"Attempting auto-unregistration of existing instance: {existing_server_key}")
516
- # Build unregistration payload using parsed server_key
517
- try:
518
- copy_number = int(existing_server_key.split("_")[-1])
519
- except Exception:
520
- copy_number = 1
521
- unregistration_data = {"server_id": self.server_id, "copy_number": copy_number}
522
- # Reuse secure unregistration request directly
523
- unreg_success, _unreg_result = await self._make_secure_unregistration_request(unregistration_data)
524
- if unreg_success:
525
- get_global_logger().info("Auto-unregistration succeeded, retrying registration once...")
526
- # Retry registration once immediately
527
- retry_success, retry_result = await self._make_secure_registration_request(registration_data)
528
- if retry_success:
529
- self.registered = True
530
- if isinstance(retry_result, dict):
531
- self.server_key = retry_result.get("server_key")
532
- else:
533
- self.server_key = None
534
- get_global_logger().info(f"✅ Successfully registered after auto-unregistration. Server key: {self.server_key}")
535
- if self.registration_config.get("heartbeat", {}).get("enabled", True):
536
- await self._start_heartbeat()
537
- return True
538
- else:
539
- get_global_logger().warning(f"Retry registration failed after auto-unregistration: {retry_result}")
540
- else:
541
- get_global_logger().warning(f"Auto-unregistration failed: {_unreg_result}")
542
- except Exception as _auto_e:
543
- get_global_logger().warning(f"Auto-unregistration/registration flow error: {_auto_e}")
544
- else:
545
- error_msg = str(result)
546
- get_global_logger().warning(
547
- f"❌ Registration attempt {attempt + 1} failed: {error_msg}"
548
- )
549
-
550
- except Exception as e:
551
- get_global_logger().error(
552
- f"❌ Registration attempt {attempt + 1} failed with exception: {e}"
553
- )
554
- get_global_logger().error(f"Full traceback: {traceback.format_exc()}")
555
-
556
- get_global_logger().error(
557
- f"❌ Failed to register with proxy after {self.retry_attempts} attempts"
558
- )
559
- return False
560
-
561
- async def unregister_server(self) -> bool:
562
- """
563
- Unregister the server from the proxy.
564
-
565
- Returns:
566
- True if unregistration was successful, False otherwise.
567
- """
568
- if not self.is_enabled():
569
- get_global_logger().info("Proxy registration is disabled, skipping unregistration")
570
- return True
571
-
572
- if not self.registered or not self.server_key:
573
- get_global_logger().info("Server not registered with proxy, skipping unregistration")
574
- return True
575
-
576
- # Stop heartbeat
577
- await self._stop_heartbeat()
578
-
579
- # Extract copy_number from server_key (format: server_id_copy_number)
580
- try:
581
- copy_number = int(self.server_key.split("_")[-1])
582
- except (ValueError, IndexError):
583
- copy_number = 1
584
-
585
- unregistration_data = {"server_id": self.server_id, "copy_number": copy_number}
586
-
587
- get_global_logger().info(f"Attempting to unregister server from proxy at {self.proxy_url}")
588
- get_global_logger().debug(f"Unregistration data: {unregistration_data}")
589
-
590
- try:
591
- success, result = await self._make_secure_unregistration_request(
592
- unregistration_data
593
- )
594
-
595
- if success:
596
- unregistered = result.get("unregistered", False)
597
- if unregistered:
598
- get_global_logger().info("✅ Successfully unregistered from proxy")
599
- else:
600
- get_global_logger().warning("⚠️ Server was not found in proxy registry")
601
-
602
- self.registered = False
603
- self.server_key = None
604
- return True
605
- else:
606
- error_msg = result.get("error", {}).get("message", "Unknown error")
607
- get_global_logger().error(f"❌ Failed to unregister from proxy: {error_msg}")
608
- return False
609
-
610
- except Exception as e:
611
- get_global_logger().error(f"❌ Unregistration failed with exception: {e}")
612
- return False
613
-
614
- async def _make_secure_registration_request(
615
- self, data: Dict[str, Any]
616
- ) -> Tuple[bool, Dict[str, Any]]:
617
- """
618
- Make secure registration request to proxy using security framework.
619
-
620
- Args:
621
- data: Registration data.
622
-
623
- Returns:
624
- Tuple of (success, result).
625
- """
626
- url = urljoin(self.proxy_url, "/register")
627
-
628
- # Get authentication headers
629
- headers = self._get_auth_headers()
630
- headers["Content-Type"] = "application/json"
631
-
632
- # Create SSL context if needed
633
- ssl_context = self._create_ssl_context()
634
-
635
- # Create connector with SSL context
636
- connector = None
637
- if ssl_context:
638
- connector = aiohttp.TCPConnector(ssl=ssl_context)
639
-
640
- try:
641
- async with aiohttp.ClientSession(connector=connector) as session:
642
- async with session.post(
643
- url,
644
- json=data,
645
- headers=headers,
646
- timeout=aiohttp.ClientTimeout(total=self.timeout),
647
- ) as response:
648
- try:
649
- result = await response.json()
650
- get_global_logger().debug(f"Response JSON parsed successfully: {type(result)} - {result}")
651
- except Exception as e:
652
- text_body = await response.text()
653
- get_global_logger().debug(f"JSON parsing failed: {e}, text_body: {text_body}")
654
- result = {"success": False, "error": {"code": "NON_JSON_RESPONSE", "message": text_body}}
655
-
656
- # Validate response headers if security framework available
657
- if self.client_security:
658
- self.client_security.validate_server_response(
659
- dict(response.headers)
660
- )
661
-
662
- # Check both HTTP status and JSON success field
663
- if response.status == 200:
664
- success = result.get("success", False)
665
- if not success:
666
- error_info = result.get("error", {})
667
- error_msg = error_info.get("message", "Unknown error")
668
- error_code = error_info.get("code", "UNKNOWN_ERROR")
669
-
670
- # Handle duplicate server URL as successful registration
671
- if error_code == "DUPLICATE_SERVER_URL":
672
- get_global_logger().info(
673
- f"✅ Server already registered: {error_msg}"
674
- )
675
- # Extract server_key from details if available
676
- details = error_info.get("details", {})
677
- existing_server_key = details.get("existing_server_key")
678
- if existing_server_key:
679
- result["server_key"] = existing_server_key
680
- get_global_logger().info(
681
- f"✅ Retrieved existing server key: {existing_server_key}"
682
- )
683
- # Return success=True for duplicate registration
684
- return True, result
685
- else:
686
- get_global_logger().warning(
687
- f"Registration failed: {error_code} - {error_msg}"
688
- )
689
- return success, result
690
- else:
691
- get_global_logger().warning(
692
- f"Registration failed with HTTP status: {response.status}"
693
- )
694
- # Ensure result is a dict for consistent error handling
695
- if isinstance(result, str):
696
- result = {"success": False, "error": {"code": "HTTP_ERROR", "message": result}}
697
- return False, result
698
- finally:
699
- if connector:
700
- await connector.close()
701
-
702
- async def _make_secure_unregistration_request(
703
- self, data: Dict[str, Any]
704
- ) -> Tuple[bool, Dict[str, Any]]:
705
- """
706
- Make secure unregistration request to proxy using security framework.
707
-
708
- Args:
709
- data: Unregistration data.
710
-
711
- Returns:
712
- Tuple of (success, result).
713
- """
714
- url = urljoin(self.proxy_url, "/unregister")
715
-
716
- # Get authentication headers
717
- headers = self._get_auth_headers()
718
- headers["Content-Type"] = "application/json"
719
-
720
- # Create SSL context if needed
721
- ssl_context = self._create_ssl_context()
722
-
723
- # Create connector with SSL context
724
- connector = None
725
- if ssl_context:
726
- connector = aiohttp.TCPConnector(ssl=ssl_context)
727
-
728
- try:
729
- async with aiohttp.ClientSession(connector=connector) as session:
730
- async with session.post(
731
- url,
732
- json=data,
733
- headers=headers,
734
- timeout=aiohttp.ClientTimeout(total=self.timeout),
735
- ) as response:
736
- result = await response.json()
737
-
738
- # Validate response headers if security framework available
739
- if self.client_security:
740
- self.client_security.validate_server_response(
741
- dict(response.headers)
742
- )
743
-
744
- # Check both HTTP status and JSON success field
745
- if response.status == 200:
746
- success = result.get("success", False)
747
- if not success:
748
- error_info = result.get("error", {})
749
- error_msg = error_info.get("message", "Unknown error")
750
- error_code = error_info.get("code", "UNKNOWN_ERROR")
751
- get_global_logger().warning(
752
- f"Unregistration failed: {error_code} - {error_msg}"
753
- )
754
- return success, result
755
- else:
756
- get_global_logger().warning(
757
- f"Unregistration failed with HTTP status: {response.status}"
758
- )
759
- return False, result
760
- finally:
761
- if connector:
762
- await connector.close()
763
-
764
- async def _start_heartbeat(self) -> None:
765
- """Start heartbeat task for keeping registration alive."""
766
- if self.heartbeat_task and not self.heartbeat_task.done():
767
- return
768
-
769
- self.heartbeat_task = asyncio.create_task(self._heartbeat_loop())
770
- get_global_logger().info("Heartbeat task started")
771
-
772
- async def _stop_heartbeat(self) -> None:
773
- """Stop heartbeat task."""
774
- if self.heartbeat_task and not self.heartbeat_task.done():
775
- self.heartbeat_task.cancel()
776
- try:
777
- await self.heartbeat_task
778
- except asyncio.CancelledError:
779
- pass
780
- get_global_logger().info("Heartbeat task stopped")
781
-
782
- async def _heartbeat_loop(self) -> None:
783
- """Heartbeat loop to keep registration alive."""
784
- while self.registered:
785
- try:
786
- await asyncio.sleep(self.heartbeat_interval)
787
-
788
- if not self.registered:
789
- break
790
-
791
- # Send heartbeat
792
- success = await self._send_heartbeat()
793
- if not success:
794
- get_global_logger().warning("Heartbeat failed, attempting to re-register")
795
- await self.register_server()
796
-
797
- except asyncio.CancelledError:
798
- break
799
- except Exception as e:
800
- get_global_logger().error(f"Heartbeat error: {e}")
801
-
802
- async def heartbeat(self) -> bool:
803
- """
804
- Public method to send heartbeat to proxy server.
805
-
806
- Returns:
807
- True if heartbeat was successful, False otherwise.
808
- """
809
- return await self._send_heartbeat()
810
-
811
- async def _send_heartbeat(self) -> bool:
812
- """Send heartbeat to proxy server."""
813
- if not self.server_key:
814
- return False
815
-
816
- url = urljoin(self.proxy_url, "/heartbeat")
817
-
818
- # Get authentication headers
819
- headers = self._get_auth_headers()
820
-
821
- # Create SSL context if needed
822
- ssl_context = self._create_ssl_context()
823
-
824
- # Create connector with SSL context
825
- connector = aiohttp.TCPConnector(ssl=ssl_context) if ssl_context else None
826
-
827
- try:
828
- timeout = aiohttp.ClientTimeout(total=self.timeout)
829
- async with aiohttp.ClientSession(connector=connector) as session:
830
- # Prefer GET heartbeat (container exposes GET /heartbeat)
831
- try:
832
- async with session.get(url, headers=headers, timeout=timeout) as resp:
833
- if resp.status == 200:
834
- get_global_logger().debug("Heartbeat (GET) succeeded")
835
- return True
836
- # If method not allowed, fall back to POST
837
- if resp.status != 405:
838
- get_global_logger().warning(
839
- f"Heartbeat (GET) failed with status: {resp.status}"
840
- )
841
- except Exception as ge:
842
- get_global_logger().debug(f"Heartbeat (GET) error: {ge}")
843
-
844
- # Fallback to POST if GET not supported
845
- heartbeat_data = {
846
- "server_id": self.server_id,
847
- "server_key": self.server_key,
848
- "timestamp": int(time.time()),
849
- }
850
- post_headers = dict(headers)
851
- post_headers["Content-Type"] = "application/json"
852
- async with session.post(
853
- url, json=heartbeat_data, headers=post_headers, timeout=timeout
854
- ) as resp:
855
- if resp.status == 200:
856
- get_global_logger().debug("Heartbeat (POST) succeeded")
857
- return True
858
- get_global_logger().warning(
859
- f"Heartbeat (POST) failed with status: {resp.status}"
860
- )
861
- return False
862
- except Exception as e:
863
- get_global_logger().error(f"Heartbeat error: {e}")
864
- return False
865
- finally:
866
- if connector:
867
- await connector.close()
868
-
869
- def get_registration_status(self) -> Dict[str, Any]:
870
- """
871
- Get current registration status.
872
-
873
- Returns:
874
- Dictionary with registration status information.
875
- """
876
- status = {
877
- "enabled": self.is_enabled(),
878
- "registered": self.registered,
879
- "server_key": self.server_key,
880
- "server_url": self.server_url,
881
- "proxy_url": self.proxy_url,
882
- "server_id": self.server_id,
883
- "heartbeat_active": self.heartbeat_task is not None
884
- and not self.heartbeat_task.done(),
885
- }
886
-
887
- # Add security information if available
888
- if self.client_security:
889
- status["security_enabled"] = True
890
- status["ssl_enabled"] = self.client_security.is_ssl_enabled()
891
- status["auth_methods"] = self.client_security.get_supported_auth_methods()
892
-
893
- cert_info = self.client_security.get_client_certificate_info()
894
- if cert_info:
895
- status["client_certificate"] = cert_info
896
- else:
897
- status["security_enabled"] = False
898
-
899
- return status
900
-
901
-
902
- # Global proxy registration manager instance (will be initialized with config)
903
- proxy_registration_manager: Optional[ProxyRegistrationManager] = None
20
+ # Global registration manager instance
21
+ _registration_manager: Optional[ProxyRegistrationManager] = None
904
22
 
905
23
 
906
24
  def initialize_proxy_registration(config: Dict[str, Any]) -> None:
907
25
  """
908
- Initialize global proxy registration manager.
26
+ Initialize proxy registration with configuration.
909
27
 
910
28
  Args:
911
29
  config: Application configuration
912
30
  """
913
- global proxy_registration_manager
914
- proxy_registration_manager = ProxyRegistrationManager(config)
31
+ global _registration_manager
32
+ _registration_manager = ProxyRegistrationManager(config)
915
33
 
916
34
 
917
35
  async def register_with_proxy(server_url: str) -> bool:
918
36
  """
919
- Register the server with the proxy.
37
+ Register server with proxy.
920
38
 
921
39
  Args:
922
- server_url: The URL where this server is accessible.
40
+ server_url: Server URL to register
923
41
 
924
42
  Returns:
925
- True if registration was successful, False otherwise.
43
+ True if registration successful, False otherwise
926
44
  """
927
- if not proxy_registration_manager:
928
- get_global_logger().error("Proxy registration manager not initialized")
45
+ if not _registration_manager:
929
46
  return False
930
-
931
- proxy_registration_manager.set_server_url(server_url)
932
- return await proxy_registration_manager.register_server()
47
+
48
+ _registration_manager.set_server_url(server_url)
49
+ return await _registration_manager.register()
933
50
 
934
51
 
935
52
  async def unregister_from_proxy() -> bool:
936
53
  """
937
- Unregister the server from the proxy.
54
+ Unregister server from proxy.
938
55
 
939
56
  Returns:
940
- True if unregistration was successful, False otherwise.
57
+ True if unregistration successful, False otherwise
941
58
  """
942
- if not proxy_registration_manager:
943
- get_global_logger().error("Proxy registration manager not initialized")
944
- return False
945
-
946
- return await proxy_registration_manager.unregister_server()
59
+ if not _registration_manager:
60
+ return True
61
+
62
+ return await _registration_manager.unregister()
947
63
 
948
64
 
949
65
  def get_proxy_registration_status() -> Dict[str, Any]:
@@ -951,9 +67,16 @@ def get_proxy_registration_status() -> Dict[str, Any]:
951
67
  Get current proxy registration status.
952
68
 
953
69
  Returns:
954
- Dictionary with registration status information.
70
+ Dictionary with registration status information
955
71
  """
956
- if not proxy_registration_manager:
957
- return {"error": "Proxy registration manager not initialized"}
958
-
959
- 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()