mcp-proxy-adapter 6.9.43__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 (242) hide show
  1. mcp_proxy_adapter/__init__.py +47 -0
  2. mcp_proxy_adapter/__main__.py +13 -0
  3. mcp_proxy_adapter/api/__init__.py +0 -0
  4. mcp_proxy_adapter/api/app.py +66 -0
  5. mcp_proxy_adapter/api/core/__init__.py +18 -0
  6. mcp_proxy_adapter/api/core/app_factory.py +355 -0
  7. mcp_proxy_adapter/api/core/lifespan_manager.py +55 -0
  8. mcp_proxy_adapter/api/core/registration_context.py +356 -0
  9. mcp_proxy_adapter/api/core/registration_manager.py +266 -0
  10. mcp_proxy_adapter/api/core/registration_tasks.py +84 -0
  11. mcp_proxy_adapter/api/core/ssl_context_factory.py +88 -0
  12. mcp_proxy_adapter/api/handlers.py +181 -0
  13. mcp_proxy_adapter/api/middleware/__init__.py +21 -0
  14. mcp_proxy_adapter/api/middleware/base.py +54 -0
  15. mcp_proxy_adapter/api/middleware/command_permission_middleware.py +73 -0
  16. mcp_proxy_adapter/api/middleware/error_handling.py +76 -0
  17. mcp_proxy_adapter/api/middleware/factory.py +147 -0
  18. mcp_proxy_adapter/api/middleware/logging.py +31 -0
  19. mcp_proxy_adapter/api/middleware/performance.py +51 -0
  20. mcp_proxy_adapter/api/middleware/protocol_middleware.py +140 -0
  21. mcp_proxy_adapter/api/middleware/transport_middleware.py +87 -0
  22. mcp_proxy_adapter/api/middleware/unified_security.py +223 -0
  23. mcp_proxy_adapter/api/middleware/user_info_middleware.py +132 -0
  24. mcp_proxy_adapter/api/openapi/__init__.py +21 -0
  25. mcp_proxy_adapter/api/openapi/command_integration.py +105 -0
  26. mcp_proxy_adapter/api/openapi/openapi_generator.py +40 -0
  27. mcp_proxy_adapter/api/openapi/openapi_registry.py +62 -0
  28. mcp_proxy_adapter/api/openapi/schema_loader.py +116 -0
  29. mcp_proxy_adapter/api/schemas.py +270 -0
  30. mcp_proxy_adapter/api/tool_integration.py +131 -0
  31. mcp_proxy_adapter/api/tools.py +163 -0
  32. mcp_proxy_adapter/cli/__init__.py +12 -0
  33. mcp_proxy_adapter/cli/commands/__init__.py +15 -0
  34. mcp_proxy_adapter/cli/commands/client.py +100 -0
  35. mcp_proxy_adapter/cli/commands/config_generate.py +35 -0
  36. mcp_proxy_adapter/cli/commands/config_validate.py +74 -0
  37. mcp_proxy_adapter/cli/commands/generate.py +259 -0
  38. mcp_proxy_adapter/cli/commands/server.py +174 -0
  39. mcp_proxy_adapter/cli/commands/sets.py +128 -0
  40. mcp_proxy_adapter/cli/commands/testconfig.py +177 -0
  41. mcp_proxy_adapter/cli/examples/__init__.py +8 -0
  42. mcp_proxy_adapter/cli/examples/http_basic.py +82 -0
  43. mcp_proxy_adapter/cli/examples/https_token.py +96 -0
  44. mcp_proxy_adapter/cli/examples/mtls_roles.py +103 -0
  45. mcp_proxy_adapter/cli/main.py +63 -0
  46. mcp_proxy_adapter/cli/parser.py +338 -0
  47. mcp_proxy_adapter/cli/validators.py +231 -0
  48. mcp_proxy_adapter/client/jsonrpc_client/__init__.py +9 -0
  49. mcp_proxy_adapter/client/jsonrpc_client/client.py +42 -0
  50. mcp_proxy_adapter/client/jsonrpc_client/command_api.py +45 -0
  51. mcp_proxy_adapter/client/jsonrpc_client/proxy_api.py +224 -0
  52. mcp_proxy_adapter/client/jsonrpc_client/queue_api.py +60 -0
  53. mcp_proxy_adapter/client/jsonrpc_client/transport.py +108 -0
  54. mcp_proxy_adapter/client/proxy.py +123 -0
  55. mcp_proxy_adapter/commands/__init__.py +66 -0
  56. mcp_proxy_adapter/commands/auth_validation_command.py +69 -0
  57. mcp_proxy_adapter/commands/base.py +389 -0
  58. mcp_proxy_adapter/commands/builtin_commands.py +30 -0
  59. mcp_proxy_adapter/commands/catalog/__init__.py +20 -0
  60. mcp_proxy_adapter/commands/catalog/catalog_loader.py +34 -0
  61. mcp_proxy_adapter/commands/catalog/catalog_manager.py +122 -0
  62. mcp_proxy_adapter/commands/catalog/catalog_syncer.py +149 -0
  63. mcp_proxy_adapter/commands/catalog/command_catalog.py +43 -0
  64. mcp_proxy_adapter/commands/catalog/dependency_manager.py +37 -0
  65. mcp_proxy_adapter/commands/catalog_manager.py +97 -0
  66. mcp_proxy_adapter/commands/cert_monitor_command.py +552 -0
  67. mcp_proxy_adapter/commands/certificate_management_command.py +562 -0
  68. mcp_proxy_adapter/commands/command_registry.py +298 -0
  69. mcp_proxy_adapter/commands/config_command.py +102 -0
  70. mcp_proxy_adapter/commands/dependency_container.py +40 -0
  71. mcp_proxy_adapter/commands/dependency_manager.py +143 -0
  72. mcp_proxy_adapter/commands/echo_command.py +48 -0
  73. mcp_proxy_adapter/commands/health_command.py +142 -0
  74. mcp_proxy_adapter/commands/help_command.py +175 -0
  75. mcp_proxy_adapter/commands/hooks.py +172 -0
  76. mcp_proxy_adapter/commands/key_management_command.py +484 -0
  77. mcp_proxy_adapter/commands/load_command.py +123 -0
  78. mcp_proxy_adapter/commands/plugins_command.py +246 -0
  79. mcp_proxy_adapter/commands/protocol_management_command.py +216 -0
  80. mcp_proxy_adapter/commands/proxy_registration_command.py +319 -0
  81. mcp_proxy_adapter/commands/queue_commands.py +750 -0
  82. mcp_proxy_adapter/commands/registration_status_command.py +76 -0
  83. mcp_proxy_adapter/commands/registry/__init__.py +18 -0
  84. mcp_proxy_adapter/commands/registry/command_info.py +103 -0
  85. mcp_proxy_adapter/commands/registry/command_loader.py +207 -0
  86. mcp_proxy_adapter/commands/registry/command_manager.py +119 -0
  87. mcp_proxy_adapter/commands/registry/command_registry.py +217 -0
  88. mcp_proxy_adapter/commands/reload_command.py +136 -0
  89. mcp_proxy_adapter/commands/result.py +157 -0
  90. mcp_proxy_adapter/commands/role_test_command.py +99 -0
  91. mcp_proxy_adapter/commands/roles_management_command.py +502 -0
  92. mcp_proxy_adapter/commands/security_command.py +472 -0
  93. mcp_proxy_adapter/commands/settings_command.py +113 -0
  94. mcp_proxy_adapter/commands/ssl_setup_command.py +306 -0
  95. mcp_proxy_adapter/commands/token_management_command.py +500 -0
  96. mcp_proxy_adapter/commands/transport_management_command.py +129 -0
  97. mcp_proxy_adapter/commands/unload_command.py +92 -0
  98. mcp_proxy_adapter/config.py +32 -0
  99. mcp_proxy_adapter/core/__init__.py +8 -0
  100. mcp_proxy_adapter/core/app_factory.py +560 -0
  101. mcp_proxy_adapter/core/app_runner.py +318 -0
  102. mcp_proxy_adapter/core/auth_validator.py +508 -0
  103. mcp_proxy_adapter/core/certificate/__init__.py +20 -0
  104. mcp_proxy_adapter/core/certificate/certificate_creator.py +372 -0
  105. mcp_proxy_adapter/core/certificate/certificate_extractor.py +185 -0
  106. mcp_proxy_adapter/core/certificate/certificate_utils.py +249 -0
  107. mcp_proxy_adapter/core/certificate/certificate_validator.py +388 -0
  108. mcp_proxy_adapter/core/certificate/ssl_context_manager.py +65 -0
  109. mcp_proxy_adapter/core/certificate_utils.py +249 -0
  110. mcp_proxy_adapter/core/client.py +608 -0
  111. mcp_proxy_adapter/core/client_manager.py +271 -0
  112. mcp_proxy_adapter/core/client_security.py +411 -0
  113. mcp_proxy_adapter/core/config/__init__.py +18 -0
  114. mcp_proxy_adapter/core/config/config.py +237 -0
  115. mcp_proxy_adapter/core/config/config_factory.py +22 -0
  116. mcp_proxy_adapter/core/config/config_loader.py +66 -0
  117. mcp_proxy_adapter/core/config/feature_manager.py +31 -0
  118. mcp_proxy_adapter/core/config/simple_config.py +116 -0
  119. mcp_proxy_adapter/core/config/simple_config_generator.py +100 -0
  120. mcp_proxy_adapter/core/config/simple_config_validator.py +380 -0
  121. mcp_proxy_adapter/core/config_converter.py +252 -0
  122. mcp_proxy_adapter/core/config_validator.py +211 -0
  123. mcp_proxy_adapter/core/crl_utils.py +362 -0
  124. mcp_proxy_adapter/core/errors.py +276 -0
  125. mcp_proxy_adapter/core/job_manager.py +54 -0
  126. mcp_proxy_adapter/core/logging.py +250 -0
  127. mcp_proxy_adapter/core/mtls_asgi.py +140 -0
  128. mcp_proxy_adapter/core/mtls_asgi_app.py +187 -0
  129. mcp_proxy_adapter/core/mtls_proxy.py +229 -0
  130. mcp_proxy_adapter/core/mtls_server.py +154 -0
  131. mcp_proxy_adapter/core/protocol_manager.py +232 -0
  132. mcp_proxy_adapter/core/proxy/__init__.py +19 -0
  133. mcp_proxy_adapter/core/proxy/auth_manager.py +26 -0
  134. mcp_proxy_adapter/core/proxy/proxy_registration_manager.py +160 -0
  135. mcp_proxy_adapter/core/proxy/registration_client.py +186 -0
  136. mcp_proxy_adapter/core/proxy/ssl_manager.py +101 -0
  137. mcp_proxy_adapter/core/proxy_client.py +184 -0
  138. mcp_proxy_adapter/core/proxy_registration.py +80 -0
  139. mcp_proxy_adapter/core/role_utils.py +103 -0
  140. mcp_proxy_adapter/core/security_adapter.py +343 -0
  141. mcp_proxy_adapter/core/security_factory.py +96 -0
  142. mcp_proxy_adapter/core/security_integration.py +342 -0
  143. mcp_proxy_adapter/core/server_adapter.py +251 -0
  144. mcp_proxy_adapter/core/server_engine.py +217 -0
  145. mcp_proxy_adapter/core/settings.py +260 -0
  146. mcp_proxy_adapter/core/signal_handler.py +107 -0
  147. mcp_proxy_adapter/core/ssl_utils.py +161 -0
  148. mcp_proxy_adapter/core/transport_manager.py +153 -0
  149. mcp_proxy_adapter/core/unified_config_adapter.py +471 -0
  150. mcp_proxy_adapter/core/utils.py +101 -0
  151. mcp_proxy_adapter/core/validation/__init__.py +21 -0
  152. mcp_proxy_adapter/core/validation/config_validator.py +219 -0
  153. mcp_proxy_adapter/core/validation/file_validator.py +131 -0
  154. mcp_proxy_adapter/core/validation/protocol_validator.py +190 -0
  155. mcp_proxy_adapter/core/validation/security_validator.py +140 -0
  156. mcp_proxy_adapter/core/validation/validation_result.py +27 -0
  157. mcp_proxy_adapter/custom_openapi.py +58 -0
  158. mcp_proxy_adapter/examples/__init__.py +16 -0
  159. mcp_proxy_adapter/examples/basic_framework/__init__.py +9 -0
  160. mcp_proxy_adapter/examples/basic_framework/commands/__init__.py +4 -0
  161. mcp_proxy_adapter/examples/basic_framework/hooks/__init__.py +4 -0
  162. mcp_proxy_adapter/examples/basic_framework/main.py +52 -0
  163. mcp_proxy_adapter/examples/bugfix_certificate_config.py +261 -0
  164. mcp_proxy_adapter/examples/cert_manager_bugfix.py +203 -0
  165. mcp_proxy_adapter/examples/check_config.py +413 -0
  166. mcp_proxy_adapter/examples/client_usage_example.py +164 -0
  167. mcp_proxy_adapter/examples/commands/__init__.py +5 -0
  168. mcp_proxy_adapter/examples/config_builder.py +234 -0
  169. mcp_proxy_adapter/examples/config_cli.py +282 -0
  170. mcp_proxy_adapter/examples/create_test_configs.py +174 -0
  171. mcp_proxy_adapter/examples/debug_request_state.py +130 -0
  172. mcp_proxy_adapter/examples/debug_role_chain.py +191 -0
  173. mcp_proxy_adapter/examples/demo_client.py +287 -0
  174. mcp_proxy_adapter/examples/full_application/__init__.py +13 -0
  175. mcp_proxy_adapter/examples/full_application/commands/__init__.py +8 -0
  176. mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +45 -0
  177. mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +52 -0
  178. mcp_proxy_adapter/examples/full_application/commands/echo_command.py +32 -0
  179. mcp_proxy_adapter/examples/full_application/commands/help_command.py +54 -0
  180. mcp_proxy_adapter/examples/full_application/commands/list_command.py +57 -0
  181. mcp_proxy_adapter/examples/full_application/hooks/__init__.py +5 -0
  182. mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +29 -0
  183. mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +27 -0
  184. mcp_proxy_adapter/examples/full_application/main.py +264 -0
  185. mcp_proxy_adapter/examples/full_application/proxy_endpoints.py +81 -0
  186. mcp_proxy_adapter/examples/full_application/run_mtls.py +252 -0
  187. mcp_proxy_adapter/examples/full_application/run_simple.py +152 -0
  188. mcp_proxy_adapter/examples/full_application/test_minimal_server.py +45 -0
  189. mcp_proxy_adapter/examples/full_application/test_server.py +163 -0
  190. mcp_proxy_adapter/examples/full_application/test_simple_server.py +62 -0
  191. mcp_proxy_adapter/examples/generate_config.py +502 -0
  192. mcp_proxy_adapter/examples/proxy_registration_example.py +335 -0
  193. mcp_proxy_adapter/examples/queue_demo_simple.py +632 -0
  194. mcp_proxy_adapter/examples/queue_integration_example.py +578 -0
  195. mcp_proxy_adapter/examples/queue_server_demo.py +82 -0
  196. mcp_proxy_adapter/examples/queue_server_example.py +85 -0
  197. mcp_proxy_adapter/examples/queue_server_simple.py +173 -0
  198. mcp_proxy_adapter/examples/required_certificates.py +208 -0
  199. mcp_proxy_adapter/examples/run_example.py +77 -0
  200. mcp_proxy_adapter/examples/run_full_test_suite.py +619 -0
  201. mcp_proxy_adapter/examples/run_proxy_server.py +153 -0
  202. mcp_proxy_adapter/examples/run_security_tests_fixed.py +435 -0
  203. mcp_proxy_adapter/examples/security_test/__init__.py +18 -0
  204. mcp_proxy_adapter/examples/security_test/auth_manager.py +14 -0
  205. mcp_proxy_adapter/examples/security_test/ssl_context_manager.py +28 -0
  206. mcp_proxy_adapter/examples/security_test/test_client.py +159 -0
  207. mcp_proxy_adapter/examples/security_test/test_result.py +22 -0
  208. mcp_proxy_adapter/examples/security_test_client.py +72 -0
  209. mcp_proxy_adapter/examples/setup/__init__.py +24 -0
  210. mcp_proxy_adapter/examples/setup/certificate_manager.py +215 -0
  211. mcp_proxy_adapter/examples/setup/config_generator.py +12 -0
  212. mcp_proxy_adapter/examples/setup/config_validator.py +118 -0
  213. mcp_proxy_adapter/examples/setup/environment_setup.py +62 -0
  214. mcp_proxy_adapter/examples/setup/test_files_generator.py +10 -0
  215. mcp_proxy_adapter/examples/setup/test_runner.py +89 -0
  216. mcp_proxy_adapter/examples/setup_test_environment.py +235 -0
  217. mcp_proxy_adapter/examples/simple_protocol_test.py +125 -0
  218. mcp_proxy_adapter/examples/test_chk_hostname_automated.py +211 -0
  219. mcp_proxy_adapter/examples/test_config.py +205 -0
  220. mcp_proxy_adapter/examples/test_config_builder.py +110 -0
  221. mcp_proxy_adapter/examples/test_examples.py +308 -0
  222. mcp_proxy_adapter/examples/test_framework_complete.py +267 -0
  223. mcp_proxy_adapter/examples/test_mcp_server.py +187 -0
  224. mcp_proxy_adapter/examples/test_protocol_examples.py +337 -0
  225. mcp_proxy_adapter/examples/universal_client.py +674 -0
  226. mcp_proxy_adapter/examples/update_config_certificates.py +135 -0
  227. mcp_proxy_adapter/examples/validate_generator_compatibility.py +385 -0
  228. mcp_proxy_adapter/examples/validate_generator_compatibility_simple.py +61 -0
  229. mcp_proxy_adapter/integrations/__init__.py +25 -0
  230. mcp_proxy_adapter/integrations/queuemgr_integration.py +462 -0
  231. mcp_proxy_adapter/main.py +313 -0
  232. mcp_proxy_adapter/openapi.py +375 -0
  233. mcp_proxy_adapter/schemas/base_schema.json +114 -0
  234. mcp_proxy_adapter/schemas/openapi_schema.json +314 -0
  235. mcp_proxy_adapter/schemas/roles.json +37 -0
  236. mcp_proxy_adapter/schemas/roles_schema.json +162 -0
  237. mcp_proxy_adapter/version.py +5 -0
  238. mcp_proxy_adapter-6.9.43.dist-info/METADATA +739 -0
  239. mcp_proxy_adapter-6.9.43.dist-info/RECORD +242 -0
  240. mcp_proxy_adapter-6.9.43.dist-info/WHEEL +5 -0
  241. mcp_proxy_adapter-6.9.43.dist-info/entry_points.txt +12 -0
  242. mcp_proxy_adapter-6.9.43.dist-info/top_level.txt +1 -0
@@ -0,0 +1,186 @@
1
+ """
2
+ Author: Vasiliy Zdanovskiy
3
+ email: vasilyvz@gmail.com
4
+
5
+ Registration client for proxy registration.
6
+ """
7
+
8
+ from typing import Dict, Any
9
+ import aiohttp
10
+
11
+ from mcp_proxy_adapter.core.logging import get_global_logger
12
+ from .auth_manager import AuthManager
13
+ from .ssl_manager import SSLManager
14
+
15
+
16
+ class RegistrationClient:
17
+ """Client for proxy registration operations."""
18
+
19
+ def __init__(
20
+ self,
21
+ client_security,
22
+ registration_config: Dict[str, Any],
23
+ config: Dict[str, Any],
24
+ proxy_url: str,
25
+ ):
26
+ """
27
+ Initialize registration client.
28
+
29
+ Args:
30
+ client_security: Client security manager instance
31
+ registration_config: Registration configuration
32
+ config: Application configuration
33
+ proxy_url: Proxy server URL
34
+ """
35
+ self.client_security = client_security
36
+ self.registration_config = registration_config
37
+ self.config = config
38
+ self.proxy_url = proxy_url
39
+ self.logger = get_global_logger()
40
+
41
+ # Initialize managers
42
+ self.auth_manager = AuthManager(client_security, registration_config)
43
+ self.ssl_manager = SSLManager(
44
+ client_security, registration_config, config, proxy_url
45
+ )
46
+
47
+ def _prepare_registration_data(self, server_url: str) -> Dict[str, Any]:
48
+ """
49
+ Prepare registration data.
50
+
51
+ Args:
52
+ server_url: Server URL to register
53
+
54
+ Returns:
55
+ Registration data dictionary
56
+ """
57
+ # Proxy expects "name" field, use server_id or server_name
58
+ server_name = (
59
+ self.registration_config.get("server_id")
60
+ or self.registration_config.get("server_name")
61
+ or "mcp_proxy_adapter"
62
+ )
63
+
64
+ return {
65
+ "name": server_name,
66
+ "url": server_url,
67
+ "capabilities": self.registration_config.get("capabilities", ["jsonrpc"]),
68
+ "metadata": {
69
+ "server_id": self.registration_config.get("server_id"),
70
+ "server_name": self.registration_config.get("server_name"),
71
+ "description": self.registration_config.get("description", ""),
72
+ "version": self.registration_config.get("version", "1.0.0"),
73
+ },
74
+ }
75
+
76
+ async def register(self, server_url: str) -> bool:
77
+ """
78
+ Register server with proxy.
79
+
80
+ Args:
81
+ server_url: Server URL to register
82
+
83
+ Returns:
84
+ True if registration successful, False otherwise
85
+ """
86
+ try:
87
+ registration_data = self._prepare_registration_data(server_url)
88
+
89
+ # Get SSL context if needed
90
+ ssl_context = self.ssl_manager.get_ssl_context()
91
+
92
+ # Get headers with authentication if needed
93
+ headers = self.auth_manager.get_headers()
94
+
95
+ # Prepare request configuration
96
+ connector = None
97
+ if ssl_context:
98
+ connector = aiohttp.TCPConnector(ssl=ssl_context)
99
+
100
+ # Send registration request
101
+ async with aiohttp.ClientSession(connector=connector) as session:
102
+ register_url = f"{self.proxy_url}/register"
103
+ self.logger.info(f"Attempting to register server with proxy at {register_url}")
104
+ self.logger.debug(f"Registration data: {registration_data}")
105
+ self.logger.debug(f"Headers: {headers}")
106
+
107
+ # Ensure Content-Type header is set
108
+ if "Content-Type" not in headers:
109
+ headers["Content-Type"] = "application/json"
110
+
111
+ async with session.post(
112
+ register_url,
113
+ json=registration_data,
114
+ headers=headers,
115
+ timeout=aiohttp.ClientTimeout(total=30)
116
+ ) as response:
117
+ if response.status == 200:
118
+ result = await response.json()
119
+ self.logger.info(f"✅ Successfully registered with proxy. Server key: {result.get('key')}")
120
+ return True
121
+ else:
122
+ error_text = await response.text()
123
+ self.logger.error(
124
+ f"❌ Failed to register with proxy: {response.status} {response.reason}: {error_text}"
125
+ )
126
+ return False
127
+
128
+ except Exception as e:
129
+ self.logger.error(f"Registration error: {e}", exc_info=True)
130
+ return False
131
+
132
+ async def unregister(self) -> bool:
133
+ """
134
+ Unregister server from proxy.
135
+
136
+ Returns:
137
+ True if unregistration successful, False otherwise
138
+ """
139
+ try:
140
+ server_name = (
141
+ self.registration_config.get("server_id")
142
+ or self.registration_config.get("server_name")
143
+ or "mcp_proxy_adapter"
144
+ )
145
+
146
+ unregister_data = {
147
+ "name": server_name,
148
+ "url": "", # Not needed for unregister
149
+ "capabilities": [],
150
+ "metadata": {},
151
+ }
152
+
153
+ # Get SSL context if needed
154
+ ssl_context = self.ssl_manager.get_ssl_context()
155
+
156
+ # Get headers with authentication if needed
157
+ headers = self.auth_manager.get_headers()
158
+
159
+ # Prepare request configuration
160
+ connector = None
161
+ if ssl_context:
162
+ connector = aiohttp.TCPConnector(ssl=ssl_context)
163
+
164
+ # Send unregistration request
165
+ async with aiohttp.ClientSession(connector=connector) as session:
166
+ unregister_url = f"{self.proxy_url}/unregister"
167
+
168
+ async with session.post(
169
+ unregister_url,
170
+ json=unregister_data,
171
+ headers=headers,
172
+ timeout=aiohttp.ClientTimeout(total=10)
173
+ ) as response:
174
+ if response.status == 200:
175
+ self.logger.info("✅ Successfully unregistered from proxy")
176
+ return True
177
+ else:
178
+ error_text = await response.text()
179
+ self.logger.warning(
180
+ f"⚠️ Failed to unregister from proxy: {response.status} {response.reason}: {error_text}"
181
+ )
182
+ return False
183
+
184
+ except Exception as e:
185
+ self.logger.error(f"Unregistration error: {e}", exc_info=True)
186
+ return False
@@ -0,0 +1,101 @@
1
+ """
2
+ Author: Vasiliy Zdanovskiy
3
+ email: vasilyvz@gmail.com
4
+
5
+ SSL management for proxy registration.
6
+ """
7
+
8
+ import ssl
9
+ from typing import Dict, Any, Optional
10
+ from urllib.parse import urlparse
11
+
12
+ from mcp_proxy_adapter.core.logging import get_global_logger
13
+
14
+
15
+ class SSLManager:
16
+ """Manager for SSL connections in proxy registration."""
17
+
18
+ def __init__(self, client_security, registration_config: Dict[str, Any], config: Dict[str, Any], proxy_url: str):
19
+ """
20
+ Initialize SSL manager.
21
+
22
+ Args:
23
+ client_security: Client security manager instance
24
+ registration_config: Registration configuration
25
+ config: Application configuration
26
+ proxy_url: Proxy server URL
27
+ """
28
+ self.client_security = client_security
29
+ self.registration_config = registration_config
30
+ self.config = config
31
+ self.proxy_url = proxy_url
32
+ self.logger = get_global_logger()
33
+
34
+ def create_ssl_context(self) -> Optional[ssl.SSLContext]:
35
+ """
36
+ Create SSL context for secure connections using registration SSL configuration.
37
+
38
+ Returns:
39
+ SSL context or None if SSL not needed
40
+ """
41
+ self.logger.debug("_create_ssl_context called")
42
+
43
+ # Decide SSL strictly by proxy URL scheme: use SSL only for https proxy URLs
44
+ try:
45
+ scheme = urlparse(self.proxy_url).scheme if self.proxy_url else "http"
46
+ if scheme.lower() != "https":
47
+ self.logger.debug("Proxy URL is HTTP, skipping SSL context creation for registration")
48
+ return None
49
+ except Exception:
50
+ self.logger.debug("Failed to parse proxy_url, assuming HTTP and skipping SSL context")
51
+ return None
52
+
53
+ if not self.client_security:
54
+ self.logger.debug("SSL context creation failed: client_security is None")
55
+ return None
56
+
57
+ try:
58
+ # Check if SSL is enabled for registration
59
+ cert_config = self.registration_config.get("certificate", {})
60
+ ssl_config = self.registration_config.get("ssl", {})
61
+
62
+ # FALLBACK: if no explicit registration SSL/certs provided, reuse global SSL config
63
+ if not cert_config and not ssl_config:
64
+ global_ssl = self.config.get("security", {}).get("ssl", {}) or self.config.get("ssl", {})
65
+ if global_ssl:
66
+ # Map global ssl to registration-style configs
67
+ mapped_cert = {}
68
+ if global_ssl.get("cert_file") and global_ssl.get("key_file"):
69
+ mapped_cert = {
70
+ "cert_file": global_ssl.get("cert_file"),
71
+ "key_file": global_ssl.get("key_file"),
72
+ }
73
+ mapped_ssl = {}
74
+ if global_ssl.get("ca_cert"):
75
+ mapped_ssl["ca_cert"] = global_ssl.get("ca_cert")
76
+ if global_ssl.get("verify_client") is not None:
77
+ mapped_ssl["verify_mode"] = (
78
+ "CERT_REQUIRED" if global_ssl.get("verify_client") else "CERT_NONE"
79
+ )
80
+ cert_config = mapped_cert
81
+ ssl_config = mapped_ssl
82
+
83
+ # Use client security manager to create SSL context
84
+ if cert_config or ssl_config:
85
+ ssl_context = self.client_security.create_ssl_context(
86
+ cert_config=cert_config,
87
+ ssl_config=ssl_config
88
+ )
89
+ if ssl_context:
90
+ self.logger.debug("SSL context created successfully for registration")
91
+ return ssl_context
92
+ else:
93
+ self.logger.warning("Failed to create SSL context for registration")
94
+ return None
95
+ else:
96
+ self.logger.debug("No SSL configuration found for registration")
97
+ return None
98
+
99
+ except Exception as e:
100
+ self.logger.error(f"Error creating SSL context for registration: {e}")
101
+ return None
@@ -0,0 +1,184 @@
1
+ """
2
+ Author: Vasiliy Zdanovskiy
3
+ email: vasilyvz@gmail.com
4
+
5
+ Core mTLS Proxy Client.
6
+
7
+ Provides an asynchronous client for communicating with a proxy-like server over
8
+ mutual TLS. Designed to be used by services built on this framework to:
9
+ - perform health/heartbeat checks
10
+ - register themselves with the proxy
11
+
12
+ This client intentionally avoids framework-specific configuration objects and
13
+ accepts explicit parameters for clarity and portability.
14
+ """
15
+
16
+ import json
17
+ import ssl
18
+ from dataclasses import dataclass
19
+ from typing import Any, Dict, Optional, Tuple
20
+ from urllib.parse import urljoin
21
+
22
+ import aiohttp # type: ignore[import]
23
+
24
+
25
+ @dataclass(frozen=True)
26
+ class RegistrationRequest:
27
+ """Data payload for registration calls to the proxy server."""
28
+
29
+ server_id: str
30
+ server_name: str
31
+ description: Optional[str] = None
32
+ extra: Optional[Dict[str, Any]] = None
33
+
34
+ def to_dict(self) -> Dict[str, Any]:
35
+ payload: Dict[str, Any] = {
36
+ "server_id": self.server_id,
37
+ "server_name": self.server_name,
38
+ }
39
+ if self.description is not None:
40
+ payload["description"] = self.description
41
+ if self.extra:
42
+ payload.update(self.extra)
43
+ return payload
44
+
45
+
46
+ class ProxyClient:
47
+ """Asynchronous mTLS HTTP client for communicating with a proxy server.
48
+
49
+ Usage:
50
+ async with ProxyClient(
51
+ base_url="https://your-proxy-host:3004",
52
+ ca_cert_path="/path/to/ca.crt",
53
+ client_cert_path="/path/to/client.crt",
54
+ client_key_path="/path/to/client.key",
55
+ ) as client:
56
+ status, health = await client.health()
57
+ status, hb = await client.heartbeat()
58
+ status, reg = await client.register(
59
+ RegistrationRequest(...)
60
+ )
61
+ """
62
+
63
+ def __init__(
64
+ self,
65
+ base_url: str,
66
+ *,
67
+ ca_cert_path: str,
68
+ client_cert_path: str,
69
+ client_key_path: str,
70
+ request_timeout_s: float = 5.0,
71
+ min_tls_version: ssl.TLSVersion = ssl.TLSVersion.TLSv1_2,
72
+ verify_mode: ssl.VerifyMode = ssl.CERT_REQUIRED,
73
+ ) -> None:
74
+ if not base_url.startswith("http"):
75
+ raise ValueError("base_url must start with http/https")
76
+ self._base_url: str = base_url.rstrip("/")
77
+ self._ca_cert_path: str = ca_cert_path
78
+ self._client_cert_path: str = client_cert_path
79
+ self._client_key_path: str = client_key_path
80
+ self._request_timeout_s: float = request_timeout_s
81
+ self._min_tls_version: ssl.TLSVersion = min_tls_version
82
+ self._verify_mode: ssl.VerifyMode = verify_mode
83
+
84
+ self._ssl_context: Optional[ssl.SSLContext] = None
85
+ self._session: Optional[aiohttp.ClientSession] = None
86
+
87
+ async def __aenter__(self) -> "ProxyClient":
88
+ await self._ensure_session()
89
+ return self
90
+
91
+ async def __aexit__(self, exc_type, exc, tb) -> None:
92
+ await self.close()
93
+
94
+ async def _ensure_session(self) -> None:
95
+ if self._session is not None:
96
+ return
97
+ ssl_context = self._build_ssl_context()
98
+ timeout = aiohttp.ClientTimeout(total=self._request_timeout_s)
99
+ connector = aiohttp.TCPConnector(ssl=ssl_context)
100
+ self._session = aiohttp.ClientSession(
101
+ timeout=timeout,
102
+ connector=connector,
103
+ )
104
+ self._ssl_context = ssl_context
105
+
106
+ async def close(self) -> None:
107
+ if self._session is not None:
108
+ await self._session.close()
109
+ self._session = None
110
+ self._ssl_context = None
111
+
112
+ def _build_ssl_context(self) -> ssl.SSLContext:
113
+ ctx = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
114
+ ctx.minimum_version = self._min_tls_version
115
+ ctx.verify_mode = self._verify_mode
116
+ ctx.load_verify_locations(self._ca_cert_path)
117
+ ctx.load_cert_chain(self._client_cert_path, self._client_key_path)
118
+ return ctx
119
+
120
+ async def _get_json(self, path: str) -> Tuple[int, Dict[str, Any]]:
121
+ await self._ensure_session()
122
+ assert self._session is not None
123
+ url = urljoin(self._base_url + "/", path.lstrip("/"))
124
+ async with self._session.get(url) as resp:
125
+ status = resp.status
126
+ body_text = await resp.text()
127
+ try:
128
+ data: Dict[str, Any] = (
129
+ json.loads(body_text) if body_text else {}
130
+ )
131
+ except json.JSONDecodeError:
132
+ data = {"raw": body_text}
133
+ return status, data
134
+
135
+ async def _post_json(
136
+ self,
137
+ path: str,
138
+ payload: Dict[str, Any],
139
+ ) -> Tuple[int, Dict[str, Any]]:
140
+ await self._ensure_session()
141
+ assert self._session is not None
142
+ url = urljoin(self._base_url + "/", path.lstrip("/"))
143
+ headers = {"Content-Type": "application/json"}
144
+ async with self._session.post(
145
+ url,
146
+ headers=headers,
147
+ json=payload,
148
+ ) as resp:
149
+ status = resp.status
150
+ body_text = await resp.text()
151
+ try:
152
+ data: Dict[str, Any] = (
153
+ json.loads(body_text) if body_text else {}
154
+ )
155
+ except json.JSONDecodeError:
156
+ data = {"raw": body_text}
157
+ return status, data
158
+
159
+ async def health(
160
+ self,
161
+ path: str = "/health",
162
+ ) -> Tuple[int, Dict[str, Any]]:
163
+ """Perform a health check against the proxy."""
164
+ return await self._get_json(path)
165
+
166
+ async def heartbeat(
167
+ self,
168
+ path: str = "/heartbeat",
169
+ ) -> Tuple[int, Dict[str, Any]]:
170
+ """Perform a heartbeat (liveness) check against the proxy."""
171
+ return await self._get_json(path)
172
+
173
+ async def register(
174
+ self,
175
+ request: RegistrationRequest,
176
+ *,
177
+ path: str = "/register",
178
+ ) -> Tuple[int, Dict[str, Any]]:
179
+ """Register the current service on the proxy server."""
180
+ payload = request.to_dict()
181
+ return await self._post_json(
182
+ path,
183
+ payload,
184
+ )
@@ -0,0 +1,80 @@
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
+ from typing import Dict, Any, Optional
13
+
14
+ from mcp_proxy_adapter.core.proxy.proxy_registration_manager import (
15
+ ProxyRegistrationManager,
16
+ )
17
+
18
+ # Global registration manager instance
19
+ _registration_manager: Optional[ProxyRegistrationManager] = None
20
+
21
+
22
+ def initialize_proxy_registration(config: Dict[str, Any]) -> None:
23
+ """
24
+ Initialize proxy registration with configuration.
25
+
26
+ Args:
27
+ config: Application configuration
28
+ """
29
+ global _registration_manager
30
+ _registration_manager = ProxyRegistrationManager(config)
31
+
32
+
33
+ async def register_with_proxy(server_url: str) -> bool:
34
+ """
35
+ Register server with proxy.
36
+
37
+ Args:
38
+ server_url: Server URL to register
39
+
40
+ Returns:
41
+ True if registration successful, False otherwise
42
+ """
43
+ if not _registration_manager:
44
+ return False
45
+
46
+ _registration_manager.set_server_url(server_url)
47
+ return await _registration_manager.register()
48
+
49
+
50
+ async def unregister_from_proxy() -> bool:
51
+ """
52
+ Unregister server from proxy.
53
+
54
+ Returns:
55
+ True if unregistration successful, False otherwise
56
+ """
57
+ if not _registration_manager:
58
+ return True
59
+
60
+ return await _registration_manager.unregister()
61
+
62
+
63
+ def get_proxy_registration_status() -> Dict[str, Any]:
64
+ """
65
+ Get current proxy registration status.
66
+
67
+ Returns:
68
+ Dictionary with registration status information
69
+ """
70
+ if not _registration_manager:
71
+ return {
72
+ "enabled": False,
73
+ "registered": False,
74
+ "proxy_url": None,
75
+ "server_url": None,
76
+ "registration_time": None,
77
+ "client_security_available": False,
78
+ }
79
+
80
+ return _registration_manager.get_registration_status()
@@ -0,0 +1,103 @@
1
+ """
2
+ Role Utilities
3
+
4
+ This module provides utilities for working with roles extracted from certificates.
5
+ Includes functions for role extraction, comparison, validation, and normalization.
6
+
7
+ Author: MCP Proxy Adapter Team
8
+ Version: 1.0.0
9
+ """
10
+
11
+ import logging
12
+ from typing import List
13
+
14
+
15
+ class RoleUtils:
16
+ """
17
+ Utilities for working with roles from certificates.
18
+
19
+ Provides methods for extracting, comparing, validating, and normalizing roles.
20
+ """
21
+
22
+ # Custom OID for roles in certificates
23
+ ROLE_EXTENSION_OID = "1.3.6.1.4.1.99999.1"
24
+
25
+ @staticmethod
26
+ def validate_single_role(role: str) -> bool:
27
+ """
28
+ Validate a single role.
29
+
30
+ Args:
31
+ role: Role string to validate
32
+
33
+ Returns:
34
+ True if role is valid, False otherwise
35
+ """
36
+ if not isinstance(role, str):
37
+ return False
38
+
39
+ # Check if role is not empty after trimming
40
+ if not role.strip():
41
+ return False
42
+
43
+ # Check for valid characters (alphanumeric, hyphens, underscores)
44
+ valid_chars = set(
45
+ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_"
46
+ )
47
+ role_chars = set(role.lower())
48
+
49
+ if not role_chars.issubset(valid_chars):
50
+ return False
51
+
52
+ # Check length (1-50 characters)
53
+ if len(role) < 1 or len(role) > 50:
54
+ return False
55
+
56
+ return True
57
+
58
+ @staticmethod
59
+ def normalize_role(role: str) -> str:
60
+ """
61
+ Normalize role string.
62
+
63
+ Args:
64
+ role: Role string to normalize
65
+
66
+ Returns:
67
+ Normalized role string
68
+ """
69
+ if not role:
70
+ return ""
71
+
72
+ # Convert to lowercase and trim whitespace
73
+ normalized = role.lower().strip()
74
+
75
+ # Replace multiple spaces with single space
76
+ normalized = " ".join(normalized.split())
77
+
78
+ # Replace spaces with hyphens
79
+ normalized = normalized.replace(" ", "-")
80
+
81
+ return normalized
82
+
83
+ @staticmethod
84
+ def normalize_roles(roles: List[str]) -> List[str]:
85
+ """
86
+ Normalize list of roles.
87
+
88
+ Args:
89
+ roles: List of roles to normalize
90
+
91
+ Returns:
92
+ List of normalized roles
93
+ """
94
+ if not roles:
95
+ return []
96
+
97
+ normalized = []
98
+ for role in roles:
99
+ normalized_role = RoleUtils.normalize_role(role)
100
+ if normalized_role and normalized_role not in normalized:
101
+ normalized.append(normalized_role)
102
+
103
+ return normalized