mcp-proxy-adapter 4.1.1__py3-none-any.whl → 6.1.0__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 (253) hide show
  1. mcp_proxy_adapter/__main__.py +12 -0
  2. mcp_proxy_adapter/api/app.py +254 -33
  3. mcp_proxy_adapter/api/handlers.py +32 -6
  4. mcp_proxy_adapter/api/middleware/__init__.py +36 -30
  5. mcp_proxy_adapter/api/middleware/command_permission_middleware.py +148 -0
  6. mcp_proxy_adapter/api/middleware/error_handling.py +9 -0
  7. mcp_proxy_adapter/api/middleware/factory.py +243 -0
  8. mcp_proxy_adapter/api/middleware/logging.py +32 -6
  9. mcp_proxy_adapter/api/middleware/protocol_middleware.py +135 -0
  10. mcp_proxy_adapter/api/middleware/transport_middleware.py +122 -0
  11. mcp_proxy_adapter/api/middleware/unified_security.py +152 -0
  12. mcp_proxy_adapter/api/middleware/user_info_middleware.py +83 -0
  13. mcp_proxy_adapter/commands/__init__.py +19 -4
  14. mcp_proxy_adapter/commands/auth_validation_command.py +408 -0
  15. mcp_proxy_adapter/commands/base.py +66 -32
  16. mcp_proxy_adapter/commands/builtin_commands.py +95 -0
  17. mcp_proxy_adapter/commands/catalog_manager.py +838 -0
  18. mcp_proxy_adapter/commands/cert_monitor_command.py +620 -0
  19. mcp_proxy_adapter/commands/certificate_management_command.py +608 -0
  20. mcp_proxy_adapter/commands/command_registry.py +711 -354
  21. mcp_proxy_adapter/commands/dependency_manager.py +245 -0
  22. mcp_proxy_adapter/commands/echo_command.py +81 -0
  23. mcp_proxy_adapter/commands/health_command.py +7 -0
  24. mcp_proxy_adapter/commands/help_command.py +21 -14
  25. mcp_proxy_adapter/commands/hooks.py +200 -167
  26. mcp_proxy_adapter/commands/key_management_command.py +506 -0
  27. mcp_proxy_adapter/commands/load_command.py +176 -0
  28. mcp_proxy_adapter/commands/plugins_command.py +235 -0
  29. mcp_proxy_adapter/commands/protocol_management_command.py +232 -0
  30. mcp_proxy_adapter/commands/proxy_registration_command.py +409 -0
  31. mcp_proxy_adapter/commands/reload_command.py +48 -50
  32. mcp_proxy_adapter/commands/result.py +1 -0
  33. mcp_proxy_adapter/commands/role_test_command.py +141 -0
  34. mcp_proxy_adapter/commands/roles_management_command.py +697 -0
  35. mcp_proxy_adapter/commands/security_command.py +488 -0
  36. mcp_proxy_adapter/commands/ssl_setup_command.py +483 -0
  37. mcp_proxy_adapter/commands/token_management_command.py +529 -0
  38. mcp_proxy_adapter/commands/transport_management_command.py +144 -0
  39. mcp_proxy_adapter/commands/unload_command.py +158 -0
  40. mcp_proxy_adapter/config.py +159 -2
  41. mcp_proxy_adapter/core/app_factory.py +326 -0
  42. mcp_proxy_adapter/core/auth_validator.py +606 -0
  43. mcp_proxy_adapter/core/certificate_utils.py +827 -0
  44. mcp_proxy_adapter/core/client_security.py +384 -0
  45. mcp_proxy_adapter/core/config_converter.py +405 -0
  46. mcp_proxy_adapter/core/config_validator.py +218 -0
  47. mcp_proxy_adapter/core/logging.py +19 -3
  48. mcp_proxy_adapter/core/mtls_asgi.py +156 -0
  49. mcp_proxy_adapter/core/mtls_asgi_app.py +187 -0
  50. mcp_proxy_adapter/core/protocol_manager.py +235 -0
  51. mcp_proxy_adapter/core/proxy_client.py +602 -0
  52. mcp_proxy_adapter/core/proxy_registration.py +522 -0
  53. mcp_proxy_adapter/core/role_utils.py +426 -0
  54. mcp_proxy_adapter/core/security_adapter.py +370 -0
  55. mcp_proxy_adapter/core/security_factory.py +239 -0
  56. mcp_proxy_adapter/core/security_integration.py +277 -0
  57. mcp_proxy_adapter/core/server_adapter.py +345 -0
  58. mcp_proxy_adapter/core/server_engine.py +364 -0
  59. mcp_proxy_adapter/core/settings.py +1 -0
  60. mcp_proxy_adapter/core/ssl_utils.py +233 -0
  61. mcp_proxy_adapter/core/transport_manager.py +292 -0
  62. mcp_proxy_adapter/core/unified_config_adapter.py +579 -0
  63. mcp_proxy_adapter/custom_openapi.py +22 -11
  64. mcp_proxy_adapter/examples/README.md +230 -97
  65. mcp_proxy_adapter/examples/README_EN.md +258 -0
  66. mcp_proxy_adapter/examples/SECURITY_TESTING.md +455 -0
  67. mcp_proxy_adapter/examples/__pycache__/security_configurations.cpython-312.pyc +0 -0
  68. mcp_proxy_adapter/examples/__pycache__/security_test_client.cpython-312.pyc +0 -0
  69. mcp_proxy_adapter/examples/basic_framework/configs/http_auth.json +37 -0
  70. mcp_proxy_adapter/examples/basic_framework/configs/http_simple.json +23 -0
  71. mcp_proxy_adapter/examples/basic_framework/configs/https_auth.json +39 -0
  72. mcp_proxy_adapter/examples/basic_framework/configs/https_simple.json +25 -0
  73. mcp_proxy_adapter/examples/basic_framework/configs/mtls_no_roles.json +39 -0
  74. mcp_proxy_adapter/examples/basic_framework/configs/mtls_with_roles.json +45 -0
  75. mcp_proxy_adapter/examples/basic_framework/main.py +63 -0
  76. mcp_proxy_adapter/examples/basic_framework/roles.json +21 -0
  77. mcp_proxy_adapter/examples/cert_config.json +9 -0
  78. mcp_proxy_adapter/examples/certs/admin.crt +32 -0
  79. mcp_proxy_adapter/examples/certs/admin.key +52 -0
  80. mcp_proxy_adapter/examples/certs/admin_cert.pem +21 -0
  81. mcp_proxy_adapter/examples/certs/admin_key.pem +28 -0
  82. mcp_proxy_adapter/examples/certs/ca_cert.pem +23 -0
  83. mcp_proxy_adapter/examples/certs/ca_cert.srl +1 -0
  84. mcp_proxy_adapter/examples/certs/ca_key.pem +28 -0
  85. mcp_proxy_adapter/examples/certs/cert_config.json +9 -0
  86. mcp_proxy_adapter/examples/certs/client.crt +32 -0
  87. mcp_proxy_adapter/examples/certs/client.key +52 -0
  88. mcp_proxy_adapter/examples/certs/client_admin.crt +32 -0
  89. mcp_proxy_adapter/examples/certs/client_admin.key +52 -0
  90. mcp_proxy_adapter/examples/certs/client_user.crt +32 -0
  91. mcp_proxy_adapter/examples/certs/client_user.key +52 -0
  92. mcp_proxy_adapter/examples/certs/guest_cert.pem +21 -0
  93. mcp_proxy_adapter/examples/certs/guest_key.pem +28 -0
  94. mcp_proxy_adapter/examples/certs/mcp_proxy_adapter_ca_ca.crt +23 -0
  95. mcp_proxy_adapter/examples/certs/proxy_cert.pem +21 -0
  96. mcp_proxy_adapter/examples/certs/proxy_key.pem +28 -0
  97. mcp_proxy_adapter/examples/certs/readonly.crt +32 -0
  98. mcp_proxy_adapter/examples/certs/readonly.key +52 -0
  99. mcp_proxy_adapter/examples/certs/readonly_cert.pem +21 -0
  100. mcp_proxy_adapter/examples/certs/readonly_key.pem +28 -0
  101. mcp_proxy_adapter/examples/certs/server.crt +32 -0
  102. mcp_proxy_adapter/examples/certs/server.key +52 -0
  103. mcp_proxy_adapter/examples/certs/server_cert.pem +32 -0
  104. mcp_proxy_adapter/examples/certs/server_key.pem +52 -0
  105. mcp_proxy_adapter/examples/certs/test_ca_ca.crt +20 -0
  106. mcp_proxy_adapter/examples/certs/user.crt +32 -0
  107. mcp_proxy_adapter/examples/certs/user.key +52 -0
  108. mcp_proxy_adapter/examples/certs/user_cert.pem +21 -0
  109. mcp_proxy_adapter/examples/certs/user_key.pem +28 -0
  110. mcp_proxy_adapter/examples/client_configs/api_key_client.json +13 -0
  111. mcp_proxy_adapter/examples/client_configs/basic_auth_client.json +13 -0
  112. mcp_proxy_adapter/examples/client_configs/certificate_client.json +22 -0
  113. mcp_proxy_adapter/examples/client_configs/jwt_client.json +15 -0
  114. mcp_proxy_adapter/examples/client_configs/no_auth_client.json +9 -0
  115. mcp_proxy_adapter/examples/commands/__init__.py +1 -0
  116. mcp_proxy_adapter/examples/create_certificates_simple.py +307 -0
  117. mcp_proxy_adapter/examples/debug_request_state.py +144 -0
  118. mcp_proxy_adapter/examples/debug_role_chain.py +205 -0
  119. mcp_proxy_adapter/examples/demo_client.py +341 -0
  120. mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +99 -0
  121. mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +106 -0
  122. mcp_proxy_adapter/examples/full_application/configs/http_auth.json +37 -0
  123. mcp_proxy_adapter/examples/full_application/configs/http_simple.json +23 -0
  124. mcp_proxy_adapter/examples/full_application/configs/https_auth.json +39 -0
  125. mcp_proxy_adapter/examples/full_application/configs/https_simple.json +25 -0
  126. mcp_proxy_adapter/examples/full_application/configs/mtls_no_roles.json +39 -0
  127. mcp_proxy_adapter/examples/full_application/configs/mtls_with_roles.json +45 -0
  128. mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +97 -0
  129. mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +95 -0
  130. mcp_proxy_adapter/examples/full_application/main.py +138 -0
  131. mcp_proxy_adapter/examples/full_application/roles.json +21 -0
  132. mcp_proxy_adapter/examples/generate_all_certificates.py +429 -0
  133. mcp_proxy_adapter/examples/generate_certificates.py +121 -0
  134. mcp_proxy_adapter/examples/keys/ca_key.pem +28 -0
  135. mcp_proxy_adapter/examples/keys/mcp_proxy_adapter_ca_ca.key +28 -0
  136. mcp_proxy_adapter/examples/keys/test_ca_ca.key +28 -0
  137. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log +220 -0
  138. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.1 +1 -0
  139. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.2 +1 -0
  140. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.3 +1 -0
  141. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.4 +1 -0
  142. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.5 +1 -0
  143. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log +220 -0
  144. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.1 +1 -0
  145. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.2 +1 -0
  146. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.3 +1 -0
  147. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.4 +1 -0
  148. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.5 +1 -0
  149. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log +2 -0
  150. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.1 +1 -0
  151. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.2 +1 -0
  152. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.3 +1 -0
  153. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.4 +1 -0
  154. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.5 +1 -0
  155. mcp_proxy_adapter/examples/proxy_registration_example.py +401 -0
  156. mcp_proxy_adapter/examples/roles.json +38 -0
  157. mcp_proxy_adapter/examples/run_example.py +81 -0
  158. mcp_proxy_adapter/examples/run_security_tests.py +326 -0
  159. mcp_proxy_adapter/examples/run_security_tests_fixed.py +300 -0
  160. mcp_proxy_adapter/examples/security_test_client.py +743 -0
  161. mcp_proxy_adapter/examples/server_configs/config_basic_http.json +204 -0
  162. mcp_proxy_adapter/examples/server_configs/config_http_token.json +238 -0
  163. mcp_proxy_adapter/examples/server_configs/config_https.json +215 -0
  164. mcp_proxy_adapter/examples/server_configs/config_https_token.json +231 -0
  165. mcp_proxy_adapter/examples/server_configs/config_mtls.json +215 -0
  166. mcp_proxy_adapter/examples/server_configs/config_proxy_registration.json +250 -0
  167. mcp_proxy_adapter/examples/server_configs/config_simple.json +46 -0
  168. mcp_proxy_adapter/examples/server_configs/roles.json +38 -0
  169. mcp_proxy_adapter/examples/test_examples.py +344 -0
  170. mcp_proxy_adapter/examples/universal_client.py +628 -0
  171. mcp_proxy_adapter/main.py +186 -0
  172. mcp_proxy_adapter/utils/config_generator.py +639 -0
  173. mcp_proxy_adapter/version.py +2 -1
  174. mcp_proxy_adapter-6.1.0.dist-info/METADATA +205 -0
  175. mcp_proxy_adapter-6.1.0.dist-info/RECORD +193 -0
  176. mcp_proxy_adapter-6.1.0.dist-info/entry_points.txt +2 -0
  177. {mcp_proxy_adapter-4.1.1.dist-info → mcp_proxy_adapter-6.1.0.dist-info}/licenses/LICENSE +2 -2
  178. mcp_proxy_adapter/api/middleware/auth.py +0 -146
  179. mcp_proxy_adapter/api/middleware/rate_limit.py +0 -152
  180. mcp_proxy_adapter/commands/reload_settings_command.py +0 -125
  181. mcp_proxy_adapter/examples/__init__.py +0 -7
  182. mcp_proxy_adapter/examples/basic_server/README.md +0 -60
  183. mcp_proxy_adapter/examples/basic_server/__init__.py +0 -7
  184. mcp_proxy_adapter/examples/basic_server/basic_custom_settings.json +0 -39
  185. mcp_proxy_adapter/examples/basic_server/config.json +0 -35
  186. mcp_proxy_adapter/examples/basic_server/custom_settings_example.py +0 -238
  187. mcp_proxy_adapter/examples/basic_server/server.py +0 -103
  188. mcp_proxy_adapter/examples/custom_commands/README.md +0 -127
  189. mcp_proxy_adapter/examples/custom_commands/__init__.py +0 -27
  190. mcp_proxy_adapter/examples/custom_commands/advanced_hooks.py +0 -250
  191. mcp_proxy_adapter/examples/custom_commands/auto_commands/__init__.py +0 -6
  192. mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_echo_command.py +0 -103
  193. mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_info_command.py +0 -111
  194. mcp_proxy_adapter/examples/custom_commands/config.json +0 -35
  195. mcp_proxy_adapter/examples/custom_commands/custom_health_command.py +0 -169
  196. mcp_proxy_adapter/examples/custom_commands/custom_help_command.py +0 -215
  197. mcp_proxy_adapter/examples/custom_commands/custom_openapi_generator.py +0 -76
  198. mcp_proxy_adapter/examples/custom_commands/custom_settings.json +0 -96
  199. mcp_proxy_adapter/examples/custom_commands/custom_settings_manager.py +0 -241
  200. mcp_proxy_adapter/examples/custom_commands/data_transform_command.py +0 -135
  201. mcp_proxy_adapter/examples/custom_commands/echo_command.py +0 -122
  202. mcp_proxy_adapter/examples/custom_commands/hooks.py +0 -230
  203. mcp_proxy_adapter/examples/custom_commands/intercept_command.py +0 -123
  204. mcp_proxy_adapter/examples/custom_commands/manual_echo_command.py +0 -103
  205. mcp_proxy_adapter/examples/custom_commands/server.py +0 -228
  206. mcp_proxy_adapter/examples/custom_commands/test_hooks.py +0 -176
  207. mcp_proxy_adapter/examples/deployment/README.md +0 -49
  208. mcp_proxy_adapter/examples/deployment/__init__.py +0 -7
  209. mcp_proxy_adapter/examples/deployment/config.development.json +0 -8
  210. mcp_proxy_adapter/examples/deployment/config.json +0 -29
  211. mcp_proxy_adapter/examples/deployment/config.production.json +0 -12
  212. mcp_proxy_adapter/examples/deployment/config.staging.json +0 -11
  213. mcp_proxy_adapter/examples/deployment/docker-compose.yml +0 -31
  214. mcp_proxy_adapter/examples/deployment/run.sh +0 -43
  215. mcp_proxy_adapter/examples/deployment/run_docker.sh +0 -84
  216. mcp_proxy_adapter/schemas/base_schema.json +0 -114
  217. mcp_proxy_adapter/schemas/openapi_schema.json +0 -314
  218. mcp_proxy_adapter/tests/__init__.py +0 -0
  219. mcp_proxy_adapter/tests/api/__init__.py +0 -3
  220. mcp_proxy_adapter/tests/api/test_cmd_endpoint.py +0 -115
  221. mcp_proxy_adapter/tests/api/test_custom_openapi.py +0 -617
  222. mcp_proxy_adapter/tests/api/test_handlers.py +0 -522
  223. mcp_proxy_adapter/tests/api/test_middleware.py +0 -340
  224. mcp_proxy_adapter/tests/api/test_schemas.py +0 -546
  225. mcp_proxy_adapter/tests/api/test_tool_integration.py +0 -531
  226. mcp_proxy_adapter/tests/commands/__init__.py +0 -3
  227. mcp_proxy_adapter/tests/commands/test_config_command.py +0 -211
  228. mcp_proxy_adapter/tests/commands/test_echo_command.py +0 -127
  229. mcp_proxy_adapter/tests/commands/test_help_command.py +0 -136
  230. mcp_proxy_adapter/tests/conftest.py +0 -131
  231. mcp_proxy_adapter/tests/functional/__init__.py +0 -3
  232. mcp_proxy_adapter/tests/functional/test_api.py +0 -253
  233. mcp_proxy_adapter/tests/integration/__init__.py +0 -3
  234. mcp_proxy_adapter/tests/integration/test_cmd_integration.py +0 -129
  235. mcp_proxy_adapter/tests/integration/test_integration.py +0 -255
  236. mcp_proxy_adapter/tests/performance/__init__.py +0 -3
  237. mcp_proxy_adapter/tests/performance/test_performance.py +0 -189
  238. mcp_proxy_adapter/tests/stubs/__init__.py +0 -10
  239. mcp_proxy_adapter/tests/stubs/echo_command.py +0 -104
  240. mcp_proxy_adapter/tests/test_api_endpoints.py +0 -271
  241. mcp_proxy_adapter/tests/test_api_handlers.py +0 -289
  242. mcp_proxy_adapter/tests/test_base_command.py +0 -123
  243. mcp_proxy_adapter/tests/test_batch_requests.py +0 -117
  244. mcp_proxy_adapter/tests/test_command_registry.py +0 -281
  245. mcp_proxy_adapter/tests/test_config.py +0 -127
  246. mcp_proxy_adapter/tests/test_utils.py +0 -65
  247. mcp_proxy_adapter/tests/unit/__init__.py +0 -3
  248. mcp_proxy_adapter/tests/unit/test_base_command.py +0 -436
  249. mcp_proxy_adapter/tests/unit/test_config.py +0 -217
  250. mcp_proxy_adapter-4.1.1.dist-info/METADATA +0 -200
  251. mcp_proxy_adapter-4.1.1.dist-info/RECORD +0 -110
  252. {mcp_proxy_adapter-4.1.1.dist-info → mcp_proxy_adapter-6.1.0.dist-info}/WHEEL +0 -0
  253. {mcp_proxy_adapter-4.1.1.dist-info → mcp_proxy_adapter-6.1.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,522 @@
1
+ """
2
+ Module for proxy registration functionality with security framework integration.
3
+
4
+ This module handles automatic registration and unregistration of the server
5
+ with the MCP proxy server during startup and shutdown, using mcp_security_framework
6
+ for secure connections and authentication.
7
+
8
+ Author: Vasiliy Zdanovskiy
9
+ email: vasilyvz@gmail.com
10
+ """
11
+
12
+ import asyncio
13
+ import json
14
+ import time
15
+ import ssl
16
+ from typing import Dict, Any, Optional, Tuple
17
+ from urllib.parse import urljoin
18
+
19
+ import aiohttp
20
+ import requests
21
+ from requests.exceptions import RequestException
22
+
23
+ from mcp_proxy_adapter.core.logging import logger
24
+ from mcp_proxy_adapter.core.client_security import create_client_security_manager
25
+
26
+
27
+ class ProxyRegistrationError(Exception):
28
+ """Exception raised when proxy registration fails."""
29
+ pass
30
+
31
+
32
+ class ProxyRegistrationManager:
33
+ """
34
+ Manager for proxy registration functionality with security framework integration.
35
+
36
+ Handles automatic registration and unregistration of the server
37
+ with the MCP proxy server using secure authentication methods.
38
+ """
39
+
40
+ def __init__(self, config: Dict[str, Any]):
41
+ """
42
+ Initialize the proxy registration manager.
43
+
44
+ Args:
45
+ config: Application configuration
46
+ """
47
+ self.config = config
48
+ self.registration_config = config.get("registration", {})
49
+
50
+ # Basic registration settings
51
+ self.proxy_url = self.registration_config.get("server_url", "https://proxy-registry.example.com")
52
+ self.server_id = self.registration_config.get("proxy_info", {}).get("name", "mcp_proxy_adapter")
53
+ self.server_name = self.registration_config.get("proxy_info", {}).get("name", "MCP Proxy Adapter")
54
+ self.description = self.registration_config.get("proxy_info", {}).get("description", "JSON-RPC API for interacting with MCP Proxy")
55
+
56
+ # Heartbeat settings
57
+ heartbeat_config = self.registration_config.get("heartbeat", {})
58
+ self.timeout = heartbeat_config.get("timeout", 30)
59
+ self.retry_attempts = heartbeat_config.get("retry_attempts", 3)
60
+ self.retry_delay = heartbeat_config.get("retry_delay", 60)
61
+ self.heartbeat_interval = heartbeat_config.get("interval", 300)
62
+
63
+ # Auto registration settings
64
+ self.auto_register = self.registration_config.get("enabled", False)
65
+ self.auto_unregister = True # Always unregister on shutdown
66
+
67
+ # Initialize client security manager
68
+ self.client_security = create_client_security_manager(config)
69
+
70
+ # Registration state
71
+ self.registered = False
72
+ self.server_key: Optional[str] = None
73
+ self.server_url: Optional[str] = None
74
+ self.heartbeat_task: Optional[asyncio.Task] = None
75
+
76
+ logger.info("Proxy registration manager initialized with security framework integration")
77
+
78
+ def is_enabled(self) -> bool:
79
+ """
80
+ Check if proxy registration is enabled.
81
+
82
+ Returns:
83
+ True if registration is enabled, False otherwise.
84
+ """
85
+ return self.registration_config.get("enabled", False)
86
+
87
+ def set_server_url(self, server_url: str) -> None:
88
+ """
89
+ Set the server URL for registration.
90
+
91
+ Args:
92
+ server_url: The URL where this server is accessible.
93
+ """
94
+ self.server_url = server_url
95
+ logger.info(f"Proxy registration server URL set to: {server_url}")
96
+
97
+ def _get_auth_headers(self) -> Dict[str, str]:
98
+ """
99
+ Get authentication headers for registration requests.
100
+
101
+ Returns:
102
+ Dictionary of authentication headers
103
+ """
104
+ if not self.client_security:
105
+ return {"Content-Type": "application/json"}
106
+
107
+ auth_method = self.registration_config.get("auth_method", "certificate")
108
+
109
+ if auth_method == "certificate":
110
+ return self.client_security.get_client_auth_headers("certificate")
111
+ elif auth_method == "token":
112
+ token_config = self.registration_config.get("token", {})
113
+ token = token_config.get("token")
114
+ return self.client_security.get_client_auth_headers("jwt", token=token)
115
+ elif auth_method == "api_key":
116
+ api_key_config = self.registration_config.get("api_key", {})
117
+ api_key = api_key_config.get("key")
118
+ return self.client_security.get_client_auth_headers("api_key", api_key=api_key)
119
+ else:
120
+ return {"Content-Type": "application/json"}
121
+
122
+ def _create_ssl_context(self) -> Optional[ssl.SSLContext]:
123
+ """
124
+ Create SSL context for secure connections.
125
+
126
+ Returns:
127
+ SSL context or None if SSL not needed
128
+ """
129
+ if not self.client_security:
130
+ return None
131
+
132
+ try:
133
+ # Check if SSL is enabled for registration
134
+ cert_config = self.registration_config.get("certificate", {})
135
+ if cert_config.get("enabled", False):
136
+ return self.client_security.create_client_ssl_context()
137
+
138
+ return None
139
+ except Exception as e:
140
+ logger.error(f"Failed to create SSL context: {e}")
141
+ return None
142
+
143
+ async def register_server(self) -> bool:
144
+ """
145
+ Register the server with the proxy using secure authentication.
146
+
147
+ Returns:
148
+ True if registration was successful, False otherwise.
149
+ """
150
+ if not self.is_enabled():
151
+ logger.info("Proxy registration is disabled in configuration")
152
+ return False
153
+
154
+ if not self.server_url:
155
+ logger.error("Server URL not set, cannot register with proxy")
156
+ return False
157
+
158
+ # Prepare registration data with proxy info
159
+ proxy_info = self.registration_config.get("proxy_info", {})
160
+ registration_data = {
161
+ "server_id": self.server_id,
162
+ "server_url": self.server_url,
163
+ "server_name": self.server_name,
164
+ "description": self.description,
165
+ "version": proxy_info.get("version", "1.0.0"),
166
+ "capabilities": proxy_info.get("capabilities", ["jsonrpc", "rest"]),
167
+ "endpoints": proxy_info.get("endpoints", {
168
+ "jsonrpc": "/api/jsonrpc",
169
+ "rest": "/cmd",
170
+ "health": "/health"
171
+ })
172
+ }
173
+
174
+ logger.info(f"Attempting to register server with proxy at {self.proxy_url}")
175
+ logger.debug(f"Registration data: {registration_data}")
176
+
177
+ for attempt in range(self.retry_attempts):
178
+ try:
179
+ success, result = await self._make_secure_registration_request(registration_data)
180
+
181
+ if success:
182
+ self.registered = True
183
+ self.server_key = result.get("server_key")
184
+ logger.info(f"✅ Successfully registered with proxy. Server key: {self.server_key}")
185
+
186
+ # Start heartbeat if enabled
187
+ if self.registration_config.get("heartbeat", {}).get("enabled", True):
188
+ await self._start_heartbeat()
189
+
190
+ return True
191
+ else:
192
+ error_msg = result.get("error", {}).get("message", "Unknown error")
193
+ logger.warning(f"❌ Registration attempt {attempt + 1} failed: {error_msg}")
194
+
195
+ if attempt < self.retry_attempts - 1:
196
+ logger.info(f"Retrying in {self.retry_delay} seconds...")
197
+ await asyncio.sleep(self.retry_delay)
198
+
199
+ except Exception as e:
200
+ logger.error(f"❌ Registration attempt {attempt + 1} failed with exception: {e}")
201
+
202
+ if attempt < self.retry_attempts - 1:
203
+ logger.info(f"Retrying in {self.retry_delay} seconds...")
204
+ await asyncio.sleep(self.retry_delay)
205
+
206
+ logger.error(f"❌ Failed to register with proxy after {self.retry_attempts} attempts")
207
+ return False
208
+
209
+ async def unregister_server(self) -> bool:
210
+ """
211
+ Unregister the server from the proxy.
212
+
213
+ Returns:
214
+ True if unregistration was successful, False otherwise.
215
+ """
216
+ if not self.is_enabled():
217
+ logger.info("Proxy registration is disabled, skipping unregistration")
218
+ return True
219
+
220
+ if not self.registered or not self.server_key:
221
+ logger.info("Server not registered with proxy, skipping unregistration")
222
+ return True
223
+
224
+ # Stop heartbeat
225
+ await self._stop_heartbeat()
226
+
227
+ # Extract copy_number from server_key (format: server_id_copy_number)
228
+ try:
229
+ copy_number = int(self.server_key.split("_")[-1])
230
+ except (ValueError, IndexError):
231
+ copy_number = 1
232
+
233
+ unregistration_data = {
234
+ "server_id": self.server_id,
235
+ "copy_number": copy_number
236
+ }
237
+
238
+ logger.info(f"Attempting to unregister server from proxy at {self.proxy_url}")
239
+ logger.debug(f"Unregistration data: {unregistration_data}")
240
+
241
+ try:
242
+ success, result = await self._make_secure_unregistration_request(unregistration_data)
243
+
244
+ if success:
245
+ unregistered = result.get("unregistered", False)
246
+ if unregistered:
247
+ logger.info("✅ Successfully unregistered from proxy")
248
+ else:
249
+ logger.warning("⚠️ Server was not found in proxy registry")
250
+
251
+ self.registered = False
252
+ self.server_key = None
253
+ return True
254
+ else:
255
+ error_msg = result.get("error", {}).get("message", "Unknown error")
256
+ logger.error(f"❌ Failed to unregister from proxy: {error_msg}")
257
+ return False
258
+
259
+ except Exception as e:
260
+ logger.error(f"❌ Unregistration failed with exception: {e}")
261
+ return False
262
+
263
+ async def _make_secure_registration_request(self, data: Dict[str, Any]) -> Tuple[bool, Dict[str, Any]]:
264
+ """
265
+ Make secure registration request to proxy using security framework.
266
+
267
+ Args:
268
+ data: Registration data.
269
+
270
+ Returns:
271
+ Tuple of (success, result).
272
+ """
273
+ url = urljoin(self.proxy_url, "/register")
274
+
275
+ # Get authentication headers
276
+ headers = self._get_auth_headers()
277
+ headers["Content-Type"] = "application/json"
278
+
279
+ # Create SSL context if needed
280
+ ssl_context = self._create_ssl_context()
281
+
282
+ # Create connector with SSL context
283
+ connector = None
284
+ if ssl_context:
285
+ connector = aiohttp.TCPConnector(ssl=ssl_context)
286
+
287
+ try:
288
+ async with aiohttp.ClientSession(connector=connector) as session:
289
+ async with session.post(
290
+ url,
291
+ json=data,
292
+ headers=headers,
293
+ timeout=aiohttp.ClientTimeout(total=self.timeout)
294
+ ) as response:
295
+ result = await response.json()
296
+
297
+ # Validate response headers if security framework available
298
+ if self.client_security:
299
+ self.client_security.validate_server_response(dict(response.headers))
300
+
301
+ return response.status == 200, result
302
+ finally:
303
+ if connector:
304
+ await connector.close()
305
+
306
+ async def _make_secure_unregistration_request(self, data: Dict[str, Any]) -> Tuple[bool, Dict[str, Any]]:
307
+ """
308
+ Make secure unregistration request to proxy using security framework.
309
+
310
+ Args:
311
+ data: Unregistration data.
312
+
313
+ Returns:
314
+ Tuple of (success, result).
315
+ """
316
+ url = urljoin(self.proxy_url, "/unregister")
317
+
318
+ # Get authentication headers
319
+ headers = self._get_auth_headers()
320
+ headers["Content-Type"] = "application/json"
321
+
322
+ # Create SSL context if needed
323
+ ssl_context = self._create_ssl_context()
324
+
325
+ # Create connector with SSL context
326
+ connector = None
327
+ if ssl_context:
328
+ connector = aiohttp.TCPConnector(ssl=ssl_context)
329
+
330
+ try:
331
+ async with aiohttp.ClientSession(connector=connector) as session:
332
+ async with session.post(
333
+ url,
334
+ json=data,
335
+ headers=headers,
336
+ timeout=aiohttp.ClientTimeout(total=self.timeout)
337
+ ) as response:
338
+ result = await response.json()
339
+
340
+ # Validate response headers if security framework available
341
+ if self.client_security:
342
+ self.client_security.validate_server_response(dict(response.headers))
343
+
344
+ return response.status == 200, result
345
+ finally:
346
+ if connector:
347
+ await connector.close()
348
+
349
+ async def _start_heartbeat(self) -> None:
350
+ """Start heartbeat task for keeping registration alive."""
351
+ if self.heartbeat_task and not self.heartbeat_task.done():
352
+ return
353
+
354
+ self.heartbeat_task = asyncio.create_task(self._heartbeat_loop())
355
+ logger.info("Heartbeat task started")
356
+
357
+ async def _stop_heartbeat(self) -> None:
358
+ """Stop heartbeat task."""
359
+ if self.heartbeat_task and not self.heartbeat_task.done():
360
+ self.heartbeat_task.cancel()
361
+ try:
362
+ await self.heartbeat_task
363
+ except asyncio.CancelledError:
364
+ pass
365
+ logger.info("Heartbeat task stopped")
366
+
367
+ async def _heartbeat_loop(self) -> None:
368
+ """Heartbeat loop to keep registration alive."""
369
+ while self.registered:
370
+ try:
371
+ await asyncio.sleep(self.heartbeat_interval)
372
+
373
+ if not self.registered:
374
+ break
375
+
376
+ # Send heartbeat
377
+ success = await self._send_heartbeat()
378
+ if not success:
379
+ logger.warning("Heartbeat failed, attempting to re-register")
380
+ await self.register_server()
381
+
382
+ except asyncio.CancelledError:
383
+ break
384
+ except Exception as e:
385
+ logger.error(f"Heartbeat error: {e}")
386
+
387
+ async def _send_heartbeat(self) -> bool:
388
+ """Send heartbeat to proxy server."""
389
+ if not self.server_key:
390
+ return False
391
+
392
+ heartbeat_data = {
393
+ "server_id": self.server_id,
394
+ "server_key": self.server_key,
395
+ "timestamp": int(time.time())
396
+ }
397
+
398
+ url = urljoin(self.proxy_url, "/heartbeat")
399
+
400
+ # Get authentication headers
401
+ headers = self._get_auth_headers()
402
+ headers["Content-Type"] = "application/json"
403
+
404
+ # Create SSL context if needed
405
+ ssl_context = self._create_ssl_context()
406
+
407
+ # Create connector with SSL context
408
+ connector = None
409
+ if ssl_context:
410
+ connector = aiohttp.TCPConnector(ssl=ssl_context)
411
+
412
+ try:
413
+ async with aiohttp.ClientSession(connector=connector) as session:
414
+ async with session.post(
415
+ url,
416
+ json=heartbeat_data,
417
+ headers=headers,
418
+ timeout=aiohttp.ClientTimeout(total=self.timeout)
419
+ ) as response:
420
+ if response.status == 200:
421
+ logger.debug("Heartbeat sent successfully")
422
+ return True
423
+ else:
424
+ logger.warning(f"Heartbeat failed with status: {response.status}")
425
+ return False
426
+ except Exception as e:
427
+ logger.error(f"Heartbeat error: {e}")
428
+ return False
429
+ finally:
430
+ if connector:
431
+ await connector.close()
432
+
433
+ def get_registration_status(self) -> Dict[str, Any]:
434
+ """
435
+ Get current registration status.
436
+
437
+ Returns:
438
+ Dictionary with registration status information.
439
+ """
440
+ status = {
441
+ "enabled": self.is_enabled(),
442
+ "registered": self.registered,
443
+ "server_key": self.server_key,
444
+ "server_url": self.server_url,
445
+ "proxy_url": self.proxy_url,
446
+ "server_id": self.server_id,
447
+ "heartbeat_active": self.heartbeat_task is not None and not self.heartbeat_task.done()
448
+ }
449
+
450
+ # Add security information if available
451
+ if self.client_security:
452
+ status["security_enabled"] = True
453
+ status["ssl_enabled"] = self.client_security.is_ssl_enabled()
454
+ status["auth_methods"] = self.client_security.get_supported_auth_methods()
455
+
456
+ cert_info = self.client_security.get_client_certificate_info()
457
+ if cert_info:
458
+ status["client_certificate"] = cert_info
459
+ else:
460
+ status["security_enabled"] = False
461
+
462
+ return status
463
+
464
+
465
+ # Global proxy registration manager instance (will be initialized with config)
466
+ proxy_registration_manager: Optional[ProxyRegistrationManager] = None
467
+
468
+
469
+ def initialize_proxy_registration(config: Dict[str, Any]) -> None:
470
+ """
471
+ Initialize global proxy registration manager.
472
+
473
+ Args:
474
+ config: Application configuration
475
+ """
476
+ global proxy_registration_manager
477
+ proxy_registration_manager = ProxyRegistrationManager(config)
478
+
479
+
480
+ async def register_with_proxy(server_url: str) -> bool:
481
+ """
482
+ Register the server with the proxy.
483
+
484
+ Args:
485
+ server_url: The URL where this server is accessible.
486
+
487
+ Returns:
488
+ True if registration was successful, False otherwise.
489
+ """
490
+ if not proxy_registration_manager:
491
+ logger.error("Proxy registration manager not initialized")
492
+ return False
493
+
494
+ proxy_registration_manager.set_server_url(server_url)
495
+ return await proxy_registration_manager.register_server()
496
+
497
+
498
+ async def unregister_from_proxy() -> bool:
499
+ """
500
+ Unregister the server from the proxy.
501
+
502
+ Returns:
503
+ True if unregistration was successful, False otherwise.
504
+ """
505
+ if not proxy_registration_manager:
506
+ logger.error("Proxy registration manager not initialized")
507
+ return False
508
+
509
+ return await proxy_registration_manager.unregister_server()
510
+
511
+
512
+ def get_proxy_registration_status() -> Dict[str, Any]:
513
+ """
514
+ Get current proxy registration status.
515
+
516
+ Returns:
517
+ Dictionary with registration status information.
518
+ """
519
+ if not proxy_registration_manager:
520
+ return {"error": "Proxy registration manager not initialized"}
521
+
522
+ return proxy_registration_manager.get_registration_status()