mcp-proxy-adapter 6.3.3__py3-none-any.whl → 6.3.5__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.
Files changed (129) hide show
  1. mcp_proxy_adapter/__init__.py +9 -5
  2. mcp_proxy_adapter/__main__.py +1 -1
  3. mcp_proxy_adapter/api/app.py +227 -176
  4. mcp_proxy_adapter/api/handlers.py +68 -60
  5. mcp_proxy_adapter/api/middleware/__init__.py +7 -5
  6. mcp_proxy_adapter/api/middleware/base.py +19 -16
  7. mcp_proxy_adapter/api/middleware/command_permission_middleware.py +44 -34
  8. mcp_proxy_adapter/api/middleware/error_handling.py +57 -67
  9. mcp_proxy_adapter/api/middleware/factory.py +50 -52
  10. mcp_proxy_adapter/api/middleware/logging.py +46 -30
  11. mcp_proxy_adapter/api/middleware/performance.py +19 -16
  12. mcp_proxy_adapter/api/middleware/protocol_middleware.py +80 -50
  13. mcp_proxy_adapter/api/middleware/transport_middleware.py +26 -24
  14. mcp_proxy_adapter/api/middleware/unified_security.py +70 -51
  15. mcp_proxy_adapter/api/middleware/user_info_middleware.py +43 -34
  16. mcp_proxy_adapter/api/schemas.py +69 -43
  17. mcp_proxy_adapter/api/tool_integration.py +83 -63
  18. mcp_proxy_adapter/api/tools.py +60 -50
  19. mcp_proxy_adapter/commands/__init__.py +15 -6
  20. mcp_proxy_adapter/commands/auth_validation_command.py +107 -110
  21. mcp_proxy_adapter/commands/base.py +108 -112
  22. mcp_proxy_adapter/commands/builtin_commands.py +28 -18
  23. mcp_proxy_adapter/commands/catalog_manager.py +394 -265
  24. mcp_proxy_adapter/commands/cert_monitor_command.py +222 -204
  25. mcp_proxy_adapter/commands/certificate_management_command.py +210 -213
  26. mcp_proxy_adapter/commands/command_registry.py +275 -226
  27. mcp_proxy_adapter/commands/config_command.py +48 -33
  28. mcp_proxy_adapter/commands/dependency_container.py +22 -23
  29. mcp_proxy_adapter/commands/dependency_manager.py +65 -56
  30. mcp_proxy_adapter/commands/echo_command.py +15 -15
  31. mcp_proxy_adapter/commands/health_command.py +31 -29
  32. mcp_proxy_adapter/commands/help_command.py +97 -61
  33. mcp_proxy_adapter/commands/hooks.py +65 -49
  34. mcp_proxy_adapter/commands/key_management_command.py +148 -147
  35. mcp_proxy_adapter/commands/load_command.py +58 -40
  36. mcp_proxy_adapter/commands/plugins_command.py +80 -54
  37. mcp_proxy_adapter/commands/protocol_management_command.py +60 -48
  38. mcp_proxy_adapter/commands/proxy_registration_command.py +107 -115
  39. mcp_proxy_adapter/commands/reload_command.py +43 -37
  40. mcp_proxy_adapter/commands/result.py +26 -33
  41. mcp_proxy_adapter/commands/role_test_command.py +26 -26
  42. mcp_proxy_adapter/commands/roles_management_command.py +176 -173
  43. mcp_proxy_adapter/commands/security_command.py +134 -122
  44. mcp_proxy_adapter/commands/settings_command.py +47 -56
  45. mcp_proxy_adapter/commands/ssl_setup_command.py +109 -129
  46. mcp_proxy_adapter/commands/token_management_command.py +129 -158
  47. mcp_proxy_adapter/commands/transport_management_command.py +41 -36
  48. mcp_proxy_adapter/commands/unload_command.py +42 -37
  49. mcp_proxy_adapter/config.py +36 -35
  50. mcp_proxy_adapter/core/__init__.py +19 -21
  51. mcp_proxy_adapter/core/app_factory.py +30 -9
  52. mcp_proxy_adapter/core/app_runner.py +81 -64
  53. mcp_proxy_adapter/core/auth_validator.py +176 -182
  54. mcp_proxy_adapter/core/certificate_utils.py +469 -426
  55. mcp_proxy_adapter/core/client.py +155 -126
  56. mcp_proxy_adapter/core/client_manager.py +60 -54
  57. mcp_proxy_adapter/core/client_security.py +108 -88
  58. mcp_proxy_adapter/core/config_converter.py +176 -143
  59. mcp_proxy_adapter/core/config_validator.py +12 -4
  60. mcp_proxy_adapter/core/crl_utils.py +21 -7
  61. mcp_proxy_adapter/core/errors.py +64 -20
  62. mcp_proxy_adapter/core/logging.py +34 -29
  63. mcp_proxy_adapter/core/mtls_asgi.py +29 -25
  64. mcp_proxy_adapter/core/mtls_asgi_app.py +66 -54
  65. mcp_proxy_adapter/core/protocol_manager.py +154 -104
  66. mcp_proxy_adapter/core/proxy_client.py +202 -144
  67. mcp_proxy_adapter/core/proxy_registration.py +12 -2
  68. mcp_proxy_adapter/core/role_utils.py +139 -125
  69. mcp_proxy_adapter/core/security_adapter.py +88 -77
  70. mcp_proxy_adapter/core/security_factory.py +50 -44
  71. mcp_proxy_adapter/core/security_integration.py +72 -24
  72. mcp_proxy_adapter/core/server_adapter.py +68 -64
  73. mcp_proxy_adapter/core/server_engine.py +71 -53
  74. mcp_proxy_adapter/core/settings.py +68 -58
  75. mcp_proxy_adapter/core/ssl_utils.py +69 -56
  76. mcp_proxy_adapter/core/transport_manager.py +72 -60
  77. mcp_proxy_adapter/core/unified_config_adapter.py +201 -150
  78. mcp_proxy_adapter/core/utils.py +4 -2
  79. mcp_proxy_adapter/custom_openapi.py +107 -99
  80. mcp_proxy_adapter/examples/basic_framework/main.py +9 -2
  81. mcp_proxy_adapter/examples/commands/__init__.py +1 -1
  82. mcp_proxy_adapter/examples/create_certificates_simple.py +182 -71
  83. mcp_proxy_adapter/examples/debug_request_state.py +38 -19
  84. mcp_proxy_adapter/examples/debug_role_chain.py +53 -20
  85. mcp_proxy_adapter/examples/demo_client.py +48 -36
  86. mcp_proxy_adapter/examples/examples/basic_framework/main.py +9 -2
  87. mcp_proxy_adapter/examples/examples/full_application/__init__.py +1 -0
  88. mcp_proxy_adapter/examples/examples/full_application/commands/custom_echo_command.py +22 -10
  89. mcp_proxy_adapter/examples/examples/full_application/commands/dynamic_calculator_command.py +24 -17
  90. mcp_proxy_adapter/examples/examples/full_application/hooks/application_hooks.py +16 -3
  91. mcp_proxy_adapter/examples/examples/full_application/hooks/builtin_command_hooks.py +13 -3
  92. mcp_proxy_adapter/examples/examples/full_application/main.py +27 -2
  93. mcp_proxy_adapter/examples/examples/full_application/proxy_endpoints.py +48 -14
  94. mcp_proxy_adapter/examples/full_application/__init__.py +1 -0
  95. mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +22 -10
  96. mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +24 -17
  97. mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +16 -3
  98. mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +13 -3
  99. mcp_proxy_adapter/examples/full_application/main.py +27 -2
  100. mcp_proxy_adapter/examples/full_application/proxy_endpoints.py +48 -14
  101. mcp_proxy_adapter/examples/generate_all_certificates.py +198 -73
  102. mcp_proxy_adapter/examples/generate_certificates.py +31 -16
  103. mcp_proxy_adapter/examples/generate_certificates_and_tokens.py +220 -74
  104. mcp_proxy_adapter/examples/generate_test_configs.py +68 -91
  105. mcp_proxy_adapter/examples/proxy_registration_example.py +76 -75
  106. mcp_proxy_adapter/examples/run_example.py +23 -5
  107. mcp_proxy_adapter/examples/run_full_test_suite.py +109 -71
  108. mcp_proxy_adapter/examples/run_proxy_server.py +22 -9
  109. mcp_proxy_adapter/examples/run_security_tests.py +103 -41
  110. mcp_proxy_adapter/examples/run_security_tests_fixed.py +72 -36
  111. mcp_proxy_adapter/examples/scripts/config_generator.py +288 -187
  112. mcp_proxy_adapter/examples/scripts/create_certificates_simple.py +185 -72
  113. mcp_proxy_adapter/examples/scripts/generate_certificates_and_tokens.py +220 -74
  114. mcp_proxy_adapter/examples/security_test_client.py +196 -127
  115. mcp_proxy_adapter/examples/setup_test_environment.py +17 -29
  116. mcp_proxy_adapter/examples/test_config.py +19 -4
  117. mcp_proxy_adapter/examples/test_config_generator.py +23 -7
  118. mcp_proxy_adapter/examples/test_examples.py +84 -56
  119. mcp_proxy_adapter/examples/universal_client.py +119 -62
  120. mcp_proxy_adapter/openapi.py +108 -115
  121. mcp_proxy_adapter/utils/config_generator.py +429 -274
  122. mcp_proxy_adapter/version.py +1 -2
  123. {mcp_proxy_adapter-6.3.3.dist-info → mcp_proxy_adapter-6.3.5.dist-info}/METADATA +1 -1
  124. mcp_proxy_adapter-6.3.5.dist-info/RECORD +143 -0
  125. mcp_proxy_adapter-6.3.3.dist-info/RECORD +0 -143
  126. {mcp_proxy_adapter-6.3.3.dist-info → mcp_proxy_adapter-6.3.5.dist-info}/WHEEL +0 -0
  127. {mcp_proxy_adapter-6.3.3.dist-info → mcp_proxy_adapter-6.3.5.dist-info}/entry_points.txt +0 -0
  128. {mcp_proxy_adapter-6.3.3.dist-info → mcp_proxy_adapter-6.3.5.dist-info}/licenses/LICENSE +0 -0
  129. {mcp_proxy_adapter-6.3.3.dist-info → mcp_proxy_adapter-6.3.5.dist-info}/top_level.txt +0 -0
@@ -24,8 +24,12 @@ try:
24
24
  from mcp_security_framework.core.client_security import ClientSecurityManager
25
25
  from mcp_security_framework.schemas.config import ClientSecurityConfig
26
26
  from mcp_security_framework.schemas.models import AuthResult, ValidationResult
27
- from mcp_security_framework.utils.crypto_utils import generate_api_key, create_jwt_token
27
+ from mcp_security_framework.utils.crypto_utils import (
28
+ generate_api_key,
29
+ create_jwt_token,
30
+ )
28
31
  from mcp_security_framework.utils.cert_utils import validate_certificate_format
32
+
29
33
  SECURITY_FRAMEWORK_AVAILABLE = True
30
34
  except ImportError:
31
35
  SECURITY_FRAMEWORK_AVAILABLE = False
@@ -39,64 +43,85 @@ from mcp_proxy_adapter.core.logging import logger
39
43
 
40
44
  class ProxyClientError(Exception):
41
45
  """Exception raised when proxy client operations fail."""
46
+
42
47
  pass
43
48
 
44
49
 
45
50
  class ProxyClient:
46
51
  """
47
52
  Client for registering with MCP proxy servers.
48
-
53
+
49
54
  Provides secure registration, heartbeat, and discovery functionality
50
55
  using mcp_security_framework for authentication and SSL/TLS.
51
56
  """
52
-
57
+
53
58
  def __init__(self, config: Dict[str, Any]):
54
59
  """
55
60
  Initialize proxy client.
56
-
61
+
57
62
  Args:
58
63
  config: Client configuration
59
64
  """
60
65
  self.config = config
61
66
  # Try both registration and proxy_registration for backward compatibility
62
- self.registration_config = config.get("registration", config.get("proxy_registration", {}))
63
-
67
+ self.registration_config = config.get(
68
+ "registration", config.get("proxy_registration", {})
69
+ )
70
+
64
71
  # Basic settings
65
- self.proxy_url = self.registration_config.get("proxy_url", self.registration_config.get("server_url"))
66
- self.server_id = self.registration_config.get("server_id", self.registration_config.get("proxy_info", {}).get("name", "mcp_proxy_adapter"))
67
- self.server_name = self.registration_config.get("server_name", self.registration_config.get("proxy_info", {}).get("name", "MCP Proxy Adapter"))
68
- self.description = self.registration_config.get("description", self.registration_config.get("proxy_info", {}).get("description", ""))
69
- self.version = self.registration_config.get("version", self.registration_config.get("proxy_info", {}).get("version", "1.0.0"))
70
-
72
+ self.proxy_url = self.registration_config.get(
73
+ "proxy_url", self.registration_config.get("server_url")
74
+ )
75
+ self.server_id = self.registration_config.get(
76
+ "server_id",
77
+ self.registration_config.get("proxy_info", {}).get(
78
+ "name", "mcp_proxy_adapter"
79
+ ),
80
+ )
81
+ self.server_name = self.registration_config.get(
82
+ "server_name",
83
+ self.registration_config.get("proxy_info", {}).get(
84
+ "name", "MCP Proxy Adapter"
85
+ ),
86
+ )
87
+ self.description = self.registration_config.get(
88
+ "description",
89
+ self.registration_config.get("proxy_info", {}).get("description", ""),
90
+ )
91
+ self.version = self.registration_config.get(
92
+ "version",
93
+ self.registration_config.get("proxy_info", {}).get("version", "1.0.0"),
94
+ )
95
+
71
96
  # Authentication settings
72
97
  self.auth_method = self.registration_config.get("auth_method", "none")
73
98
  self.auth_config = self._get_auth_config()
74
-
99
+
75
100
  # Heartbeat settings
76
101
  heartbeat_config = self.registration_config.get("heartbeat", {})
77
102
  self.heartbeat_interval = heartbeat_config.get("interval", 300)
78
103
  self.heartbeat_timeout = heartbeat_config.get("timeout", 30)
79
104
  self.retry_attempts = heartbeat_config.get("retry_attempts", 3)
80
105
  self.retry_delay = heartbeat_config.get("retry_delay", 60)
81
-
106
+
82
107
  # Auto discovery settings
83
108
  discovery_config = self.registration_config.get("auto_discovery", {})
84
109
  self.discovery_enabled = discovery_config.get("enabled", False)
85
110
  self.discovery_urls = discovery_config.get("discovery_urls", [])
86
111
  self.discovery_interval = discovery_config.get("discovery_interval", 3600)
87
-
112
+
88
113
  # Initialize security manager
89
114
  self.security_manager = self._create_security_manager()
90
-
115
+
91
116
  # State
92
117
  self.registered = False
93
118
  self.server_key: Optional[str] = None
94
119
  self.server_url: Optional[str] = None
95
120
  self.heartbeat_task: Optional[asyncio.Task] = None
96
121
  self.discovery_task: Optional[asyncio.Task] = None
97
-
122
+
98
123
  logger.info("Proxy client initialized with security framework integration")
99
-
124
+
100
125
  def _get_auth_config(self) -> Dict[str, Any]:
101
126
  """Get authentication configuration based on auth method."""
102
127
  if self.auth_method == "certificate":
@@ -107,75 +132,85 @@ class ProxyClient:
107
132
  return self.registration_config.get("api_key", {})
108
133
  else:
109
134
  return {}
110
-
135
+
111
136
  def _create_security_manager(self) -> Optional[ClientSecurityManager]:
112
137
  """Create client security manager."""
113
138
  if not SECURITY_FRAMEWORK_AVAILABLE:
114
139
  logger.warning("mcp_security_framework not available, using basic client")
115
140
  return None
116
-
141
+
117
142
  try:
118
143
  # Create client security configuration
119
144
  client_security_config = self.registration_config.get("client_security", {})
120
-
145
+
121
146
  if not client_security_config.get("enabled", False):
122
147
  logger.info("Client security disabled in configuration")
123
148
  return None
124
-
149
+
125
150
  # Create security config
126
151
  security_config = {
127
152
  "security": {
128
153
  "ssl": {
129
154
  "enabled": client_security_config.get("ssl_enabled", False),
130
- "client_cert_file": client_security_config.get("certificate_auth", {}).get("cert_file"),
131
- "client_key_file": client_security_config.get("certificate_auth", {}).get("key_file"),
132
- "ca_cert_file": client_security_config.get("certificate_auth", {}).get("ca_cert_file"),
155
+ "client_cert_file": client_security_config.get(
156
+ "certificate_auth", {}
157
+ ).get("cert_file"),
158
+ "client_key_file": client_security_config.get(
159
+ "certificate_auth", {}
160
+ ).get("key_file"),
161
+ "ca_cert_file": client_security_config.get(
162
+ "certificate_auth", {}
163
+ ).get("ca_cert_file"),
133
164
  "verify_mode": "CERT_REQUIRED",
134
165
  "min_tls_version": "TLSv1.2",
135
166
  "check_hostname": True,
136
- "check_expiry": True
167
+ "check_expiry": True,
137
168
  },
138
169
  "auth": {
139
170
  "enabled": True,
140
- "methods": client_security_config.get("auth_methods", ["api_key"]),
171
+ "methods": client_security_config.get(
172
+ "auth_methods", ["api_key"]
173
+ ),
141
174
  "api_keys": {
142
- client_security_config.get("api_key_auth", {}).get("key", "default"): {
175
+ client_security_config.get("api_key_auth", {}).get(
176
+ "key", "default"
177
+ ): {
143
178
  "roles": ["proxy_client"],
144
- "permissions": ["register", "heartbeat", "discover"]
179
+ "permissions": ["register", "heartbeat", "discover"],
145
180
  }
146
- }
147
- }
181
+ },
182
+ },
148
183
  }
149
184
  }
150
-
185
+
151
186
  return ClientSecurityManager(security_config)
152
-
187
+
153
188
  except Exception as e:
154
189
  logger.error(f"Failed to create security manager: {e}")
155
190
  return None
156
-
191
+
157
192
  def set_server_url(self, server_url: str) -> None:
158
193
  """
159
194
  Set the server URL for registration.
160
-
195
+
161
196
  Args:
162
197
  server_url: The URL where this server is accessible.
163
198
  """
164
199
  self.server_url = server_url
165
200
  logger.info(f"Proxy client server URL set to: {server_url}")
166
-
201
+
167
202
  def _get_auth_headers(self) -> Dict[str, str]:
168
203
  """
169
204
  Get authentication headers for requests.
170
-
205
+
171
206
  Returns:
172
207
  Dictionary of authentication headers
173
208
  """
174
209
  headers = {"Content-Type": "application/json"}
175
-
210
+
176
211
  if not self.security_manager:
177
212
  return headers
178
-
213
+
179
214
  try:
180
215
  if self.auth_method == "certificate":
181
216
  return self.security_manager.get_client_auth_headers("certificate")
@@ -184,44 +219,46 @@ class ProxyClient:
184
219
  return self.security_manager.get_client_auth_headers("jwt", token=token)
185
220
  elif self.auth_method == "api_key":
186
221
  api_key = self.auth_config.get("key")
187
- return self.security_manager.get_client_auth_headers("api_key", api_key=api_key)
222
+ return self.security_manager.get_client_auth_headers(
223
+ "api_key", api_key=api_key
224
+ )
188
225
  else:
189
226
  return headers
190
227
  except Exception as e:
191
228
  logger.error(f"Failed to get auth headers: {e}")
192
229
  return headers
193
-
230
+
194
231
  def _create_ssl_context(self) -> Optional[ssl.SSLContext]:
195
232
  """
196
233
  Create SSL context for secure connections.
197
-
234
+
198
235
  Returns:
199
236
  SSL context or None if SSL not needed
200
237
  """
201
238
  if not self.security_manager:
202
239
  return None
203
-
240
+
204
241
  try:
205
242
  return self.security_manager.create_client_ssl_context()
206
243
  except Exception as e:
207
244
  logger.error(f"Failed to create SSL context: {e}")
208
245
  return None
209
-
246
+
210
247
  async def register(self) -> bool:
211
248
  """
212
249
  Register with the proxy server.
213
-
250
+
214
251
  Returns:
215
252
  True if registration was successful, False otherwise.
216
253
  """
217
254
  if not self.proxy_url:
218
255
  logger.error("Proxy URL not configured")
219
256
  return False
220
-
257
+
221
258
  if not self.server_url:
222
259
  logger.error("Server URL not set")
223
260
  return False
224
-
261
+
225
262
  # Prepare registration data
226
263
  proxy_info = self.registration_config.get("proxy_info", {})
227
264
  registration_data = {
@@ -231,86 +268,94 @@ class ProxyClient:
231
268
  "description": self.description,
232
269
  "version": self.version,
233
270
  "capabilities": proxy_info.get("capabilities", ["jsonrpc", "rest"]),
234
- "endpoints": proxy_info.get("endpoints", {
235
- "jsonrpc": "/api/jsonrpc",
236
- "rest": "/cmd",
237
- "health": "/health"
238
- }),
271
+ "endpoints": proxy_info.get(
272
+ "endpoints",
273
+ {"jsonrpc": "/api/jsonrpc", "rest": "/cmd", "health": "/health"},
274
+ ),
239
275
  "auth_method": self.auth_method,
240
- "security_enabled": self.security_manager is not None
276
+ "security_enabled": self.security_manager is not None,
241
277
  }
242
-
278
+
243
279
  logger.info(f"Attempting to register with proxy at {self.proxy_url}")
244
280
  logger.debug(f"Registration data: {registration_data}")
245
-
281
+
246
282
  for attempt in range(self.retry_attempts):
247
283
  try:
248
- success, result = await self._make_request("/register", registration_data)
249
-
284
+ success, result = await self._make_request(
285
+ "/register", registration_data
286
+ )
287
+
250
288
  if success:
251
289
  self.registered = True
252
290
  self.server_key = result.get("server_key")
253
- logger.info(f"✅ Successfully registered with proxy. Server key: {self.server_key}")
254
-
291
+ logger.info(
292
+ f"✅ Successfully registered with proxy. Server key: {self.server_key}"
293
+ )
294
+
255
295
  # Start heartbeat and discovery
256
296
  await self._start_background_tasks()
257
-
297
+
258
298
  return True
259
299
  else:
260
300
  error_msg = result.get("error", {}).get("message", "Unknown error")
261
- logger.warning(f"❌ Registration attempt {attempt + 1} failed: {error_msg}")
262
-
301
+ logger.warning(
302
+ f"❌ Registration attempt {attempt + 1} failed: {error_msg}"
303
+ )
304
+
263
305
  if attempt < self.retry_attempts - 1:
264
306
  logger.info(f"Retrying in {self.retry_delay} seconds...")
265
307
  await asyncio.sleep(self.retry_delay)
266
-
308
+
267
309
  except Exception as e:
268
- logger.error(f"❌ Registration attempt {attempt + 1} failed with exception: {e}")
269
-
310
+ logger.error(
311
+ f"❌ Registration attempt {attempt + 1} failed with exception: {e}"
312
+ )
313
+
270
314
  if attempt < self.retry_attempts - 1:
271
315
  logger.info(f"Retrying in {self.retry_delay} seconds...")
272
316
  await asyncio.sleep(self.retry_delay)
273
-
274
- logger.error(f"❌ Failed to register with proxy after {self.retry_attempts} attempts")
317
+
318
+ logger.error(
319
+ f"❌ Failed to register with proxy after {self.retry_attempts} attempts"
320
+ )
275
321
  return False
276
-
322
+
277
323
  async def unregister(self) -> bool:
278
324
  """
279
325
  Unregister from the proxy server.
280
-
326
+
281
327
  Returns:
282
328
  True if unregistration was successful, False otherwise.
283
329
  """
284
330
  if not self.registered or not self.server_key:
285
331
  logger.info("Not registered with proxy, skipping unregistration")
286
332
  return True
287
-
333
+
288
334
  # Stop background tasks
289
335
  await self._stop_background_tasks()
290
-
336
+
291
337
  # Extract copy_number from server_key
292
338
  try:
293
339
  copy_number = int(self.server_key.split("_")[-1])
294
340
  except (ValueError, IndexError):
295
341
  copy_number = 1
296
-
297
- unregistration_data = {
298
- "server_id": self.server_id,
299
- "copy_number": copy_number
300
- }
301
-
342
+
343
+ unregistration_data = {"server_id": self.server_id, "copy_number": copy_number}
344
+
302
345
  logger.info(f"Attempting to unregister from proxy at {self.proxy_url}")
303
-
346
+
304
347
  try:
305
- success, result = await self._make_request("/unregister", unregistration_data)
306
-
348
+ success, result = await self._make_request(
349
+ "/unregister", unregistration_data
350
+ )
351
+
307
352
  if success:
308
353
  unregistered = result.get("unregistered", False)
309
354
  if unregistered:
310
355
  logger.info("✅ Successfully unregistered from proxy")
311
356
  else:
312
357
  logger.warning("⚠️ Server was not found in proxy registry")
313
-
358
+
314
359
  self.registered = False
315
360
  self.server_key = None
316
361
  return True
@@ -318,111 +363,118 @@ class ProxyClient:
318
363
  error_msg = result.get("error", {}).get("message", "Unknown error")
319
364
  logger.error(f"❌ Failed to unregister from proxy: {error_msg}")
320
365
  return False
321
-
366
+
322
367
  except Exception as e:
323
368
  logger.error(f"❌ Unregistration failed with exception: {e}")
324
369
  return False
325
-
370
+
326
371
  async def send_heartbeat(self) -> bool:
327
372
  """
328
373
  Send heartbeat to proxy server.
329
-
374
+
330
375
  Returns:
331
376
  True if heartbeat was successful, False otherwise.
332
377
  """
333
378
  if not self.server_key:
334
379
  return False
335
-
380
+
336
381
  heartbeat_data = {
337
382
  "server_id": self.server_id,
338
383
  "server_key": self.server_key,
339
384
  "timestamp": int(time.time()),
340
- "status": "healthy"
385
+ "status": "healthy",
341
386
  }
342
-
387
+
343
388
  try:
344
389
  success, result = await self._make_request("/heartbeat", heartbeat_data)
345
-
390
+
346
391
  if success:
347
392
  logger.debug("Heartbeat sent successfully")
348
393
  return True
349
394
  else:
350
- logger.warning(f"Heartbeat failed: {result.get('error', {}).get('message', 'Unknown error')}")
395
+ logger.warning(
396
+ f"Heartbeat failed: {result.get('error', {}).get('message', 'Unknown error')}"
397
+ )
351
398
  return False
352
-
399
+
353
400
  except Exception as e:
354
401
  logger.error(f"Heartbeat error: {e}")
355
402
  return False
356
-
403
+
357
404
  async def discover_proxies(self) -> List[Dict[str, Any]]:
358
405
  """
359
406
  Discover available proxy servers.
360
-
407
+
361
408
  Returns:
362
409
  List of discovered proxy servers.
363
410
  """
364
411
  if not self.discovery_enabled:
365
412
  return []
366
-
413
+
367
414
  discovered_proxies = []
368
-
415
+
369
416
  for discovery_url in self.discovery_urls:
370
417
  try:
371
- success, result = await self._make_request("/discover", {}, base_url=discovery_url)
372
-
418
+ success, result = await self._make_request(
419
+ "/discover", {}, base_url=discovery_url
420
+ )
421
+
373
422
  if success:
374
423
  proxies = result.get("proxies", [])
375
424
  discovered_proxies.extend(proxies)
376
- logger.info(f"Discovered {len(proxies)} proxies from {discovery_url}")
425
+ logger.info(
426
+ f"Discovered {len(proxies)} proxies from {discovery_url}"
427
+ )
377
428
  else:
378
429
  logger.warning(f"Discovery failed for {discovery_url}")
379
-
430
+
380
431
  except Exception as e:
381
432
  logger.error(f"Discovery error for {discovery_url}: {e}")
382
-
433
+
383
434
  return discovered_proxies
384
-
385
- async def _make_request(self, endpoint: str, data: Dict[str, Any], base_url: Optional[str] = None) -> Tuple[bool, Dict[str, Any]]:
435
+
436
+ async def _make_request(
437
+ self, endpoint: str, data: Dict[str, Any], base_url: Optional[str] = None
438
+ ) -> Tuple[bool, Dict[str, Any]]:
386
439
  """
387
440
  Make HTTP request to proxy server.
388
-
441
+
389
442
  Args:
390
443
  endpoint: API endpoint
391
444
  data: Request data
392
445
  base_url: Base URL (optional, uses self.proxy_url if not provided)
393
-
446
+
394
447
  Returns:
395
448
  Tuple of (success, result)
396
449
  """
397
450
  url = urljoin(base_url or self.proxy_url, endpoint)
398
-
451
+
399
452
  # Get authentication headers
400
453
  headers = self._get_auth_headers()
401
-
454
+
402
455
  # Create SSL context if needed
403
456
  ssl_context = self._create_ssl_context()
404
-
457
+
405
458
  # Create connector with SSL context
406
459
  connector = None
407
460
  if ssl_context:
408
461
  connector = TCPConnector(ssl=ssl_context)
409
-
462
+
410
463
  try:
411
464
  timeout = ClientTimeout(total=self.heartbeat_timeout)
412
-
465
+
413
466
  async with aiohttp.ClientSession(connector=connector) as session:
414
467
  async with session.post(
415
- url,
416
- json=data,
417
- headers=headers,
418
- timeout=timeout
468
+ url, json=data, headers=headers, timeout=timeout
419
469
  ) as response:
420
470
  result = await response.json()
421
-
471
+
422
472
  # Validate response if security manager available
423
473
  if self.security_manager:
424
- self.security_manager.validate_server_response(dict(response.headers))
425
-
474
+ self.security_manager.validate_server_response(
475
+ dict(response.headers)
476
+ )
477
+
426
478
  return response.status == 200, result
427
479
  except Exception as e:
428
480
  logger.error(f"Request failed: {e}")
@@ -430,19 +482,19 @@ class ProxyClient:
430
482
  finally:
431
483
  if connector:
432
484
  await connector.close()
433
-
485
+
434
486
  async def _start_background_tasks(self) -> None:
435
487
  """Start heartbeat and discovery background tasks."""
436
488
  # Start heartbeat
437
489
  if self.registration_config.get("heartbeat", {}).get("enabled", True):
438
490
  self.heartbeat_task = asyncio.create_task(self._heartbeat_loop())
439
491
  logger.info("Heartbeat task started")
440
-
492
+
441
493
  # Start discovery
442
494
  if self.discovery_enabled:
443
495
  self.discovery_task = asyncio.create_task(self._discovery_loop())
444
496
  logger.info("Discovery task started")
445
-
497
+
446
498
  async def _stop_background_tasks(self) -> None:
447
499
  """Stop background tasks."""
448
500
  # Stop heartbeat
@@ -453,7 +505,7 @@ class ProxyClient:
453
505
  except asyncio.CancelledError:
454
506
  pass
455
507
  logger.info("Heartbeat task stopped")
456
-
508
+
457
509
  # Stop discovery
458
510
  if self.discovery_task and not self.discovery_task.done():
459
511
  self.discovery_task.cancel()
@@ -462,62 +514,66 @@ class ProxyClient:
462
514
  except asyncio.CancelledError:
463
515
  pass
464
516
  logger.info("Discovery task stopped")
465
-
517
+
466
518
  async def _heartbeat_loop(self) -> None:
467
519
  """Heartbeat loop to keep registration alive."""
468
520
  while self.registered:
469
521
  try:
470
522
  await asyncio.sleep(self.heartbeat_interval)
471
-
523
+
472
524
  if not self.registered:
473
525
  break
474
-
526
+
475
527
  # Send heartbeat
476
528
  success = await self.send_heartbeat()
477
529
  if not success:
478
530
  logger.warning("Heartbeat failed, attempting to re-register")
479
531
  await self.register()
480
-
532
+
481
533
  except asyncio.CancelledError:
482
534
  break
483
535
  except Exception as e:
484
536
  logger.error(f"Heartbeat error: {e}")
485
-
537
+
486
538
  async def _discovery_loop(self) -> None:
487
539
  """Discovery loop to find new proxy servers."""
488
540
  while self.registered:
489
541
  try:
490
542
  await asyncio.sleep(self.discovery_interval)
491
-
543
+
492
544
  if not self.registered:
493
545
  break
494
-
546
+
495
547
  # Discover proxies
496
548
  proxies = await self.discover_proxies()
497
549
  if proxies:
498
550
  logger.info(f"Discovered {len(proxies)} proxy servers")
499
-
551
+
500
552
  # Register with new proxies if configured
501
- if self.registration_config.get("auto_discovery", {}).get("register_on_discovery", False):
553
+ if self.registration_config.get("auto_discovery", {}).get(
554
+ "register_on_discovery", False
555
+ ):
502
556
  for proxy in proxies:
503
557
  proxy_url = proxy.get("url")
504
558
  if proxy_url and proxy_url != self.proxy_url:
505
- logger.info(f"Attempting to register with discovered proxy: {proxy_url}")
559
+ logger.info(
560
+ f"Attempting to register with discovered proxy: {proxy_url}"
561
+ )
506
562
  # Store original URL and try to register with new proxy
507
563
  original_url = self.proxy_url
508
564
  self.proxy_url = proxy_url
509
565
  await self.register()
510
566
  self.proxy_url = original_url
511
-
567
+
512
568
  except asyncio.CancelledError:
513
569
  break
514
570
  except Exception as e:
515
571
  logger.error(f"Discovery error: {e}")
516
-
572
+
517
573
  def get_status(self) -> Dict[str, Any]:
518
574
  """
519
575
  Get current client status.
520
-
576
+
521
577
  Returns:
522
578
  Dictionary with client status information.
523
579
  """
@@ -529,10 +585,12 @@ class ProxyClient:
529
585
  "proxy_url": self.proxy_url,
530
586
  "server_id": self.server_id,
531
587
  "auth_method": self.auth_method,
532
- "heartbeat_active": self.heartbeat_task is not None and not self.heartbeat_task.done(),
533
- "discovery_active": self.discovery_task is not None and not self.discovery_task.done()
588
+ "heartbeat_active": self.heartbeat_task is not None
589
+ and not self.heartbeat_task.done(),
590
+ "discovery_active": self.discovery_task is not None
591
+ and not self.discovery_task.done(),
534
592
  }
535
-
593
+
536
594
  # Add security information
537
595
  if self.security_manager:
538
596
  status["security_enabled"] = True
@@ -540,7 +598,7 @@ class ProxyClient:
540
598
  status["auth_methods"] = self.security_manager.get_supported_auth_methods()
541
599
  else:
542
600
  status["security_enabled"] = False
543
-
601
+
544
602
  return status
545
603
 
546
604
 
@@ -551,7 +609,7 @@ proxy_client: Optional[ProxyClient] = None
551
609
  def initialize_proxy_client(config: Dict[str, Any]) -> None:
552
610
  """
553
611
  Initialize global proxy client.
554
-
612
+
555
613
  Args:
556
614
  config: Application configuration
557
615
  """
@@ -562,17 +620,17 @@ def initialize_proxy_client(config: Dict[str, Any]) -> None:
562
620
  async def register_with_proxy(server_url: str) -> bool:
563
621
  """
564
622
  Register with proxy server.
565
-
623
+
566
624
  Args:
567
625
  server_url: The URL where this server is accessible.
568
-
626
+
569
627
  Returns:
570
628
  True if registration was successful, False otherwise.
571
629
  """
572
630
  if not proxy_client:
573
631
  logger.error("Proxy client not initialized")
574
632
  return False
575
-
633
+
576
634
  proxy_client.set_server_url(server_url)
577
635
  return await proxy_client.register()
578
636
 
@@ -580,25 +638,25 @@ async def register_with_proxy(server_url: str) -> bool:
580
638
  async def unregister_from_proxy() -> bool:
581
639
  """
582
640
  Unregister from proxy server.
583
-
641
+
584
642
  Returns:
585
643
  True if unregistration was successful, False otherwise.
586
644
  """
587
645
  if not proxy_client:
588
646
  logger.error("Proxy client not initialized")
589
647
  return False
590
-
648
+
591
649
  return await proxy_client.unregister()
592
650
 
593
651
 
594
652
  def get_proxy_client_status() -> Dict[str, Any]:
595
653
  """
596
654
  Get proxy client status.
597
-
655
+
598
656
  Returns:
599
657
  Dictionary with client status information.
600
658
  """
601
659
  if not proxy_client:
602
660
  return {"error": "Proxy client not initialized"}
603
-
661
+
604
662
  return proxy_client.get_status()