mcp-proxy-adapter 6.0.0__py3-none-any.whl → 6.0.1__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 (212) hide show
  1. mcp_proxy_adapter/__main__.py +27 -7
  2. mcp_proxy_adapter/api/app.py +209 -79
  3. mcp_proxy_adapter/api/handlers.py +16 -5
  4. mcp_proxy_adapter/api/middleware/__init__.py +14 -9
  5. mcp_proxy_adapter/api/middleware/command_permission_middleware.py +148 -0
  6. mcp_proxy_adapter/api/middleware/factory.py +36 -12
  7. mcp_proxy_adapter/api/middleware/protocol_middleware.py +84 -18
  8. mcp_proxy_adapter/api/middleware/unified_security.py +197 -0
  9. mcp_proxy_adapter/api/middleware/user_info_middleware.py +158 -0
  10. mcp_proxy_adapter/commands/__init__.py +7 -1
  11. mcp_proxy_adapter/commands/base.py +7 -4
  12. mcp_proxy_adapter/commands/builtin_commands.py +8 -2
  13. mcp_proxy_adapter/commands/command_registry.py +8 -0
  14. mcp_proxy_adapter/commands/echo_command.py +81 -0
  15. mcp_proxy_adapter/commands/health_command.py +1 -1
  16. mcp_proxy_adapter/commands/help_command.py +21 -14
  17. mcp_proxy_adapter/commands/proxy_registration_command.py +326 -185
  18. mcp_proxy_adapter/commands/role_test_command.py +141 -0
  19. mcp_proxy_adapter/commands/security_command.py +488 -0
  20. mcp_proxy_adapter/commands/ssl_setup_command.py +234 -351
  21. mcp_proxy_adapter/commands/token_management_command.py +1 -1
  22. mcp_proxy_adapter/config.py +323 -40
  23. mcp_proxy_adapter/core/app_factory.py +410 -0
  24. mcp_proxy_adapter/core/app_runner.py +272 -0
  25. mcp_proxy_adapter/core/certificate_utils.py +291 -73
  26. mcp_proxy_adapter/core/client.py +574 -0
  27. mcp_proxy_adapter/core/client_manager.py +284 -0
  28. mcp_proxy_adapter/core/client_security.py +384 -0
  29. mcp_proxy_adapter/core/logging.py +8 -3
  30. mcp_proxy_adapter/core/mtls_asgi.py +156 -0
  31. mcp_proxy_adapter/core/mtls_asgi_app.py +187 -0
  32. mcp_proxy_adapter/core/protocol_manager.py +169 -10
  33. mcp_proxy_adapter/core/proxy_client.py +602 -0
  34. mcp_proxy_adapter/core/proxy_registration.py +299 -47
  35. mcp_proxy_adapter/core/security_adapter.py +12 -15
  36. mcp_proxy_adapter/core/security_integration.py +286 -0
  37. mcp_proxy_adapter/core/server_adapter.py +282 -0
  38. mcp_proxy_adapter/core/server_engine.py +270 -0
  39. mcp_proxy_adapter/core/ssl_utils.py +13 -12
  40. mcp_proxy_adapter/core/transport_manager.py +5 -5
  41. mcp_proxy_adapter/core/unified_config_adapter.py +579 -0
  42. mcp_proxy_adapter/examples/__init__.py +13 -4
  43. mcp_proxy_adapter/examples/basic_framework/__init__.py +9 -0
  44. mcp_proxy_adapter/examples/basic_framework/commands/__init__.py +4 -0
  45. mcp_proxy_adapter/examples/basic_framework/hooks/__init__.py +4 -0
  46. mcp_proxy_adapter/examples/basic_framework/main.py +44 -0
  47. mcp_proxy_adapter/examples/commands/__init__.py +5 -0
  48. mcp_proxy_adapter/examples/create_certificates_simple.py +550 -0
  49. mcp_proxy_adapter/examples/debug_request_state.py +112 -0
  50. mcp_proxy_adapter/examples/debug_role_chain.py +158 -0
  51. mcp_proxy_adapter/examples/demo_client.py +275 -0
  52. mcp_proxy_adapter/examples/examples/basic_framework/__init__.py +9 -0
  53. mcp_proxy_adapter/examples/examples/basic_framework/commands/__init__.py +4 -0
  54. mcp_proxy_adapter/examples/examples/basic_framework/hooks/__init__.py +4 -0
  55. mcp_proxy_adapter/examples/examples/basic_framework/main.py +44 -0
  56. mcp_proxy_adapter/examples/examples/full_application/__init__.py +12 -0
  57. mcp_proxy_adapter/examples/examples/full_application/commands/__init__.py +7 -0
  58. mcp_proxy_adapter/examples/examples/full_application/commands/custom_echo_command.py +80 -0
  59. mcp_proxy_adapter/examples/examples/full_application/commands/dynamic_calculator_command.py +90 -0
  60. mcp_proxy_adapter/examples/examples/full_application/hooks/__init__.py +7 -0
  61. mcp_proxy_adapter/examples/examples/full_application/hooks/application_hooks.py +75 -0
  62. mcp_proxy_adapter/examples/examples/full_application/hooks/builtin_command_hooks.py +71 -0
  63. mcp_proxy_adapter/examples/examples/full_application/main.py +173 -0
  64. mcp_proxy_adapter/examples/examples/full_application/proxy_endpoints.py +154 -0
  65. mcp_proxy_adapter/examples/full_application/__init__.py +12 -0
  66. mcp_proxy_adapter/examples/full_application/commands/__init__.py +7 -0
  67. mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +80 -0
  68. mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +90 -0
  69. mcp_proxy_adapter/examples/full_application/hooks/__init__.py +7 -0
  70. mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +75 -0
  71. mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +71 -0
  72. mcp_proxy_adapter/examples/full_application/main.py +173 -0
  73. mcp_proxy_adapter/examples/full_application/proxy_endpoints.py +154 -0
  74. mcp_proxy_adapter/examples/generate_all_certificates.py +362 -0
  75. mcp_proxy_adapter/examples/generate_certificates.py +177 -0
  76. mcp_proxy_adapter/examples/generate_certificates_and_tokens.py +369 -0
  77. mcp_proxy_adapter/examples/generate_test_configs.py +331 -0
  78. mcp_proxy_adapter/examples/proxy_registration_example.py +334 -0
  79. mcp_proxy_adapter/examples/run_example.py +59 -0
  80. mcp_proxy_adapter/examples/run_full_test_suite.py +318 -0
  81. mcp_proxy_adapter/examples/run_proxy_server.py +146 -0
  82. mcp_proxy_adapter/examples/run_security_tests.py +544 -0
  83. mcp_proxy_adapter/examples/run_security_tests_fixed.py +247 -0
  84. mcp_proxy_adapter/examples/scripts/config_generator.py +740 -0
  85. mcp_proxy_adapter/examples/scripts/create_certificates_simple.py +560 -0
  86. mcp_proxy_adapter/examples/scripts/generate_certificates_and_tokens.py +369 -0
  87. mcp_proxy_adapter/examples/security_test_client.py +782 -0
  88. mcp_proxy_adapter/examples/setup_test_environment.py +328 -0
  89. mcp_proxy_adapter/examples/test_config.py +148 -0
  90. mcp_proxy_adapter/examples/test_config_generator.py +86 -0
  91. mcp_proxy_adapter/examples/test_examples.py +281 -0
  92. mcp_proxy_adapter/examples/universal_client.py +620 -0
  93. mcp_proxy_adapter/main.py +66 -148
  94. mcp_proxy_adapter/utils/config_generator.py +1008 -0
  95. mcp_proxy_adapter/version.py +5 -2
  96. mcp_proxy_adapter-6.0.1.dist-info/METADATA +679 -0
  97. mcp_proxy_adapter-6.0.1.dist-info/RECORD +140 -0
  98. mcp_proxy_adapter-6.0.1.dist-info/entry_points.txt +2 -0
  99. {mcp_proxy_adapter-6.0.0.dist-info → mcp_proxy_adapter-6.0.1.dist-info}/licenses/LICENSE +2 -2
  100. mcp_proxy_adapter/api/middleware/auth.py +0 -146
  101. mcp_proxy_adapter/api/middleware/auth_adapter.py +0 -235
  102. mcp_proxy_adapter/api/middleware/mtls_adapter.py +0 -305
  103. mcp_proxy_adapter/api/middleware/mtls_middleware.py +0 -296
  104. mcp_proxy_adapter/api/middleware/rate_limit.py +0 -152
  105. mcp_proxy_adapter/api/middleware/rate_limit_adapter.py +0 -241
  106. mcp_proxy_adapter/api/middleware/roles_adapter.py +0 -365
  107. mcp_proxy_adapter/api/middleware/roles_middleware.py +0 -381
  108. mcp_proxy_adapter/api/middleware/security.py +0 -376
  109. mcp_proxy_adapter/api/middleware/token_auth_middleware.py +0 -261
  110. mcp_proxy_adapter/examples/README.md +0 -124
  111. mcp_proxy_adapter/examples/basic_server/README.md +0 -60
  112. mcp_proxy_adapter/examples/basic_server/__init__.py +0 -7
  113. mcp_proxy_adapter/examples/basic_server/basic_custom_settings.json +0 -39
  114. mcp_proxy_adapter/examples/basic_server/config.json +0 -70
  115. mcp_proxy_adapter/examples/basic_server/config_all_protocols.json +0 -54
  116. mcp_proxy_adapter/examples/basic_server/config_http.json +0 -70
  117. mcp_proxy_adapter/examples/basic_server/config_http_only.json +0 -52
  118. mcp_proxy_adapter/examples/basic_server/config_https.json +0 -58
  119. mcp_proxy_adapter/examples/basic_server/config_mtls.json +0 -58
  120. mcp_proxy_adapter/examples/basic_server/config_ssl.json +0 -46
  121. mcp_proxy_adapter/examples/basic_server/custom_settings_example.py +0 -238
  122. mcp_proxy_adapter/examples/basic_server/server.py +0 -114
  123. mcp_proxy_adapter/examples/custom_commands/README.md +0 -127
  124. mcp_proxy_adapter/examples/custom_commands/__init__.py +0 -27
  125. mcp_proxy_adapter/examples/custom_commands/advanced_hooks.py +0 -566
  126. mcp_proxy_adapter/examples/custom_commands/auto_commands/__init__.py +0 -6
  127. mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_echo_command.py +0 -103
  128. mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_info_command.py +0 -111
  129. mcp_proxy_adapter/examples/custom_commands/auto_commands/test_command.py +0 -105
  130. mcp_proxy_adapter/examples/custom_commands/catalog/commands/test_command.py +0 -129
  131. mcp_proxy_adapter/examples/custom_commands/config.json +0 -118
  132. mcp_proxy_adapter/examples/custom_commands/config_all_protocols.json +0 -46
  133. mcp_proxy_adapter/examples/custom_commands/config_https_only.json +0 -46
  134. mcp_proxy_adapter/examples/custom_commands/config_https_transport.json +0 -33
  135. mcp_proxy_adapter/examples/custom_commands/config_mtls_only.json +0 -46
  136. mcp_proxy_adapter/examples/custom_commands/config_mtls_transport.json +0 -33
  137. mcp_proxy_adapter/examples/custom_commands/config_single_transport.json +0 -33
  138. mcp_proxy_adapter/examples/custom_commands/custom_health_command.py +0 -169
  139. mcp_proxy_adapter/examples/custom_commands/custom_help_command.py +0 -215
  140. mcp_proxy_adapter/examples/custom_commands/custom_openapi_generator.py +0 -76
  141. mcp_proxy_adapter/examples/custom_commands/custom_settings.json +0 -96
  142. mcp_proxy_adapter/examples/custom_commands/custom_settings_manager.py +0 -241
  143. mcp_proxy_adapter/examples/custom_commands/data_transform_command.py +0 -135
  144. mcp_proxy_adapter/examples/custom_commands/echo_command.py +0 -122
  145. mcp_proxy_adapter/examples/custom_commands/full_help_response.json +0 -1
  146. mcp_proxy_adapter/examples/custom_commands/generated_openapi.json +0 -629
  147. mcp_proxy_adapter/examples/custom_commands/get_openapi.py +0 -103
  148. mcp_proxy_adapter/examples/custom_commands/hooks.py +0 -230
  149. mcp_proxy_adapter/examples/custom_commands/intercept_command.py +0 -123
  150. mcp_proxy_adapter/examples/custom_commands/loadable_commands/test_ignored.py +0 -129
  151. mcp_proxy_adapter/examples/custom_commands/manual_echo_command.py +0 -103
  152. mcp_proxy_adapter/examples/custom_commands/proxy_connection_manager.py +0 -278
  153. mcp_proxy_adapter/examples/custom_commands/server.py +0 -252
  154. mcp_proxy_adapter/examples/custom_commands/simple_openapi_server.py +0 -75
  155. mcp_proxy_adapter/examples/custom_commands/start_server_with_proxy_manager.py +0 -299
  156. mcp_proxy_adapter/examples/custom_commands/start_server_with_registration.py +0 -278
  157. mcp_proxy_adapter/examples/custom_commands/test_hooks.py +0 -176
  158. mcp_proxy_adapter/examples/custom_commands/test_openapi.py +0 -27
  159. mcp_proxy_adapter/examples/custom_commands/test_registry.py +0 -23
  160. mcp_proxy_adapter/examples/custom_commands/test_simple.py +0 -19
  161. mcp_proxy_adapter/examples/custom_project_example/README.md +0 -103
  162. mcp_proxy_adapter/examples/custom_project_example/README_EN.md +0 -103
  163. mcp_proxy_adapter/examples/deployment/README.md +0 -49
  164. mcp_proxy_adapter/examples/deployment/__init__.py +0 -7
  165. mcp_proxy_adapter/examples/deployment/config.development.json +0 -8
  166. mcp_proxy_adapter/examples/deployment/config.json +0 -29
  167. mcp_proxy_adapter/examples/deployment/config.production.json +0 -12
  168. mcp_proxy_adapter/examples/deployment/config.staging.json +0 -11
  169. mcp_proxy_adapter/examples/deployment/docker-compose.yml +0 -31
  170. mcp_proxy_adapter/examples/deployment/run.sh +0 -43
  171. mcp_proxy_adapter/examples/deployment/run_docker.sh +0 -84
  172. mcp_proxy_adapter/examples/simple_custom_commands/README.md +0 -149
  173. mcp_proxy_adapter/examples/simple_custom_commands/README_EN.md +0 -149
  174. mcp_proxy_adapter/schemas/base_schema.json +0 -114
  175. mcp_proxy_adapter/schemas/openapi_schema.json +0 -314
  176. mcp_proxy_adapter/schemas/roles_schema.json +0 -162
  177. mcp_proxy_adapter/tests/__init__.py +0 -0
  178. mcp_proxy_adapter/tests/api/__init__.py +0 -3
  179. mcp_proxy_adapter/tests/api/test_cmd_endpoint.py +0 -115
  180. mcp_proxy_adapter/tests/api/test_custom_openapi.py +0 -617
  181. mcp_proxy_adapter/tests/api/test_handlers.py +0 -522
  182. mcp_proxy_adapter/tests/api/test_middleware.py +0 -340
  183. mcp_proxy_adapter/tests/api/test_schemas.py +0 -546
  184. mcp_proxy_adapter/tests/api/test_tool_integration.py +0 -531
  185. mcp_proxy_adapter/tests/commands/__init__.py +0 -3
  186. mcp_proxy_adapter/tests/commands/test_config_command.py +0 -211
  187. mcp_proxy_adapter/tests/commands/test_echo_command.py +0 -127
  188. mcp_proxy_adapter/tests/commands/test_help_command.py +0 -136
  189. mcp_proxy_adapter/tests/conftest.py +0 -131
  190. mcp_proxy_adapter/tests/functional/__init__.py +0 -3
  191. mcp_proxy_adapter/tests/functional/test_api.py +0 -253
  192. mcp_proxy_adapter/tests/integration/__init__.py +0 -3
  193. mcp_proxy_adapter/tests/integration/test_cmd_integration.py +0 -129
  194. mcp_proxy_adapter/tests/integration/test_integration.py +0 -255
  195. mcp_proxy_adapter/tests/performance/__init__.py +0 -3
  196. mcp_proxy_adapter/tests/performance/test_performance.py +0 -189
  197. mcp_proxy_adapter/tests/stubs/__init__.py +0 -10
  198. mcp_proxy_adapter/tests/stubs/echo_command.py +0 -104
  199. mcp_proxy_adapter/tests/test_api_endpoints.py +0 -271
  200. mcp_proxy_adapter/tests/test_api_handlers.py +0 -289
  201. mcp_proxy_adapter/tests/test_base_command.py +0 -123
  202. mcp_proxy_adapter/tests/test_batch_requests.py +0 -117
  203. mcp_proxy_adapter/tests/test_command_registry.py +0 -281
  204. mcp_proxy_adapter/tests/test_config.py +0 -127
  205. mcp_proxy_adapter/tests/test_utils.py +0 -65
  206. mcp_proxy_adapter/tests/unit/__init__.py +0 -3
  207. mcp_proxy_adapter/tests/unit/test_base_command.py +0 -436
  208. mcp_proxy_adapter/tests/unit/test_config.py +0 -270
  209. mcp_proxy_adapter-6.0.0.dist-info/METADATA +0 -201
  210. mcp_proxy_adapter-6.0.0.dist-info/RECORD +0 -179
  211. {mcp_proxy_adapter-6.0.0.dist-info → mcp_proxy_adapter-6.0.1.dist-info}/WHEEL +0 -0
  212. {mcp_proxy_adapter-6.0.0.dist-info → mcp_proxy_adapter-6.0.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,284 @@
1
+ """
2
+ Client Manager for MCP Proxy Adapter Framework
3
+
4
+ This module provides client management functionality for the MCP Proxy Adapter framework.
5
+ It handles client creation, connection management, and proxy registration.
6
+
7
+ Author: Vasiliy Zdanovskiy
8
+ email: vasilyvz@gmail.com
9
+ """
10
+
11
+ import asyncio
12
+ import json
13
+ import logging
14
+ from typing import Dict, Any, Optional, List
15
+ from pathlib import Path
16
+
17
+ from .client import UniversalClient, create_client_from_config
18
+
19
+
20
+ class ClientManager:
21
+ """
22
+ Manages client connections and proxy registrations.
23
+
24
+ This class provides functionality for:
25
+ - Creating and managing client connections
26
+ - Proxy registration
27
+ - Connection pooling
28
+ - Authentication management
29
+ """
30
+
31
+ def __init__(self, config: Dict[str, Any]):
32
+ """
33
+ Initialize client manager.
34
+
35
+ Args:
36
+ config: Client manager configuration
37
+ """
38
+ self.config = config
39
+ self.logger = logging.getLogger(__name__)
40
+ self.clients: Dict[str, UniversalClient] = {}
41
+ self.connection_pool: Dict[str, UniversalClient] = {}
42
+
43
+ # Client manager settings
44
+ self.max_connections = config.get("max_connections", 10)
45
+ self.connection_timeout = config.get("connection_timeout", 30)
46
+ self.retry_attempts = config.get("retry_attempts", 3)
47
+ self.retry_delay = config.get("retry_delay", 1)
48
+
49
+ self.logger.info("Client manager initialized")
50
+
51
+ async def create_client(self, client_id: str, config_file: str) -> UniversalClient:
52
+ """
53
+ Create a new client instance.
54
+
55
+ Args:
56
+ client_id: Unique identifier for the client
57
+ config_file: Path to client configuration file
58
+
59
+ Returns:
60
+ UniversalClient instance
61
+ """
62
+ try:
63
+ if client_id in self.clients:
64
+ self.logger.warning(f"Client {client_id} already exists, reusing existing connection")
65
+ return self.clients[client_id]
66
+
67
+ client = create_client_from_config(config_file)
68
+ self.clients[client_id] = client
69
+
70
+ self.logger.info(f"Client {client_id} created successfully")
71
+ return client
72
+
73
+ except Exception as e:
74
+ self.logger.error(f"Failed to create client {client_id}: {e}")
75
+ raise
76
+
77
+ async def get_client(self, client_id: str) -> Optional[UniversalClient]:
78
+ """
79
+ Get an existing client instance.
80
+
81
+ Args:
82
+ client_id: Client identifier
83
+
84
+ Returns:
85
+ UniversalClient instance or None if not found
86
+ """
87
+ return self.clients.get(client_id)
88
+
89
+ async def remove_client(self, client_id: str) -> bool:
90
+ """
91
+ Remove a client instance.
92
+
93
+ Args:
94
+ client_id: Client identifier
95
+
96
+ Returns:
97
+ True if client was removed, False otherwise
98
+ """
99
+ if client_id in self.clients:
100
+ client = self.clients[client_id]
101
+ await client.disconnect()
102
+ del self.clients[client_id]
103
+ self.logger.info(f"Client {client_id} removed")
104
+ return True
105
+ return False
106
+
107
+ async def test_client_connection(self, client_id: str) -> bool:
108
+ """
109
+ Test connection for a specific client.
110
+
111
+ Args:
112
+ client_id: Client identifier
113
+
114
+ Returns:
115
+ True if connection is successful, False otherwise
116
+ """
117
+ client = await self.get_client(client_id)
118
+ if not client:
119
+ self.logger.error(f"Client {client_id} not found")
120
+ return False
121
+
122
+ try:
123
+ return await client.test_connection()
124
+ except Exception as e:
125
+ self.logger.error(f"Connection test failed for client {client_id}: {e}")
126
+ return False
127
+
128
+ async def register_proxy(self, client_id: str, proxy_config: Dict[str, Any]) -> Dict[str, Any]:
129
+ """
130
+ Register with proxy server using a specific client.
131
+
132
+ Args:
133
+ client_id: Client identifier
134
+ proxy_config: Proxy registration configuration
135
+
136
+ Returns:
137
+ Registration result
138
+ """
139
+ client = await self.get_client(client_id)
140
+ if not client:
141
+ return {"error": f"Client {client_id} not found"}
142
+
143
+ try:
144
+ result = await client.register_proxy(proxy_config)
145
+ self.logger.info(f"Proxy registration completed for client {client_id}")
146
+ return result
147
+ except Exception as e:
148
+ self.logger.error(f"Proxy registration failed for client {client_id}: {e}")
149
+ return {"error": str(e)}
150
+
151
+ async def execute_command(self, client_id: str, command: str, params: Dict[str, Any] = None) -> Dict[str, Any]:
152
+ """
153
+ Execute a command using a specific client.
154
+
155
+ Args:
156
+ client_id: Client identifier
157
+ command: Command name
158
+ params: Command parameters
159
+
160
+ Returns:
161
+ Command result
162
+ """
163
+ client = await self.get_client(client_id)
164
+ if not client:
165
+ return {"error": f"Client {client_id} not found"}
166
+
167
+ try:
168
+ result = await client.execute_command(command, params or {})
169
+ self.logger.info(f"Command {command} executed for client {client_id}")
170
+ return result
171
+ except Exception as e:
172
+ self.logger.error(f"Command execution failed for client {client_id}: {e}")
173
+ return {"error": str(e)}
174
+
175
+ async def get_client_status(self, client_id: str) -> Dict[str, Any]:
176
+ """
177
+ Get status information for a specific client.
178
+
179
+ Args:
180
+ client_id: Client identifier
181
+
182
+ Returns:
183
+ Client status information
184
+ """
185
+ client = await self.get_client(client_id)
186
+ if not client:
187
+ return {"error": f"Client {client_id} not found"}
188
+
189
+ try:
190
+ # Test connection
191
+ connection_ok = await client.test_connection()
192
+
193
+ # Test security features
194
+ security_features = await client.test_security_features()
195
+
196
+ status = {
197
+ "client_id": client_id,
198
+ "base_url": client.base_url,
199
+ "auth_method": client.auth_method,
200
+ "connection_ok": connection_ok,
201
+ "security_features": security_features,
202
+ "session_active": client.session is not None
203
+ }
204
+
205
+ return status
206
+ except Exception as e:
207
+ self.logger.error(f"Failed to get status for client {client_id}: {e}")
208
+ return {"error": str(e)}
209
+
210
+ async def list_clients(self) -> List[str]:
211
+ """
212
+ Get list of all client identifiers.
213
+
214
+ Returns:
215
+ List of client identifiers
216
+ """
217
+ return list(self.clients.keys())
218
+
219
+ async def cleanup(self):
220
+ """Clean up all client connections."""
221
+ for client_id in list(self.clients.keys()):
222
+ await self.remove_client(client_id)
223
+ self.logger.info("All client connections cleaned up")
224
+
225
+ async def __aenter__(self):
226
+ """Async context manager entry."""
227
+ return self
228
+
229
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
230
+ """Async context manager exit."""
231
+ await self.cleanup()
232
+
233
+
234
+ def create_client_manager(config: Dict[str, Any]) -> ClientManager:
235
+ """
236
+ Create a ClientManager instance.
237
+
238
+ Args:
239
+ config: Client manager configuration
240
+
241
+ Returns:
242
+ ClientManager instance
243
+ """
244
+ return ClientManager(config)
245
+
246
+
247
+ # Example usage and testing functions
248
+ async def test_client_manager():
249
+ """Test client manager functionality."""
250
+ # Example configuration
251
+ config = {
252
+ "max_connections": 5,
253
+ "connection_timeout": 30,
254
+ "retry_attempts": 3,
255
+ "retry_delay": 1
256
+ }
257
+
258
+ async with ClientManager(config) as manager:
259
+ # Create a client
260
+ client_id = "test_client"
261
+ config_file = "configs/http_simple.json"
262
+
263
+ try:
264
+ client = await manager.create_client(client_id, config_file)
265
+ print(f"✅ Client {client_id} created successfully")
266
+
267
+ # Test connection
268
+ connection_ok = await manager.test_client_connection(client_id)
269
+ print(f"✅ Connection test: {connection_ok}")
270
+
271
+ # Get status
272
+ status = await manager.get_client_status(client_id)
273
+ print(f"✅ Client status: {json.dumps(status, indent=2)}")
274
+
275
+ # Execute command
276
+ result = await manager.execute_command(client_id, "help")
277
+ print(f"✅ Command result: {json.dumps(result, indent=2)}")
278
+
279
+ except Exception as e:
280
+ print(f"❌ Test failed: {e}")
281
+
282
+
283
+ if __name__ == "__main__":
284
+ asyncio.run(test_client_manager())
@@ -0,0 +1,384 @@
1
+ """
2
+ Client Security Module
3
+
4
+ This module provides client-side security integration for MCP Proxy Adapter,
5
+ using mcp_security_framework utilities for secure connections to servers.
6
+
7
+ Author: Vasiliy Zdanovskiy
8
+ email: vasilyvz@gmail.com
9
+ """
10
+
11
+ import logging
12
+ import ssl
13
+ from typing import Dict, Any, Optional, List, Tuple
14
+ from pathlib import Path
15
+
16
+ # Import framework utilities
17
+ try:
18
+ from mcp_security_framework.utils.crypto_utils import (
19
+ generate_api_key, create_jwt_token, verify_jwt_token
20
+ )
21
+ from mcp_security_framework.utils.cert_utils import (
22
+ parse_certificate, extract_roles_from_certificate,
23
+ validate_certificate_chain, validate_certificate_format
24
+ )
25
+ from mcp_security_framework.core.ssl_manager import SSLManager
26
+ from mcp_security_framework.schemas.config import SSLConfig, AuthConfig
27
+ from mcp_security_framework.schemas.models import AuthResult, ValidationResult
28
+ SECURITY_FRAMEWORK_AVAILABLE = True
29
+ except ImportError:
30
+ SECURITY_FRAMEWORK_AVAILABLE = False
31
+ SSLManager = None
32
+ SSLConfig = None
33
+ AuthConfig = None
34
+ AuthResult = None
35
+ ValidationResult = None
36
+
37
+ from mcp_proxy_adapter.core.logging import logger
38
+
39
+
40
+ class ClientSecurityManager:
41
+ """
42
+ Client-side security manager for MCP Proxy Adapter.
43
+
44
+ Provides secure client connections using mcp_security_framework utilities.
45
+ Handles authentication, certificate management, and SSL/TLS for client connections.
46
+ """
47
+
48
+ def __init__(self, config: Dict[str, Any]):
49
+ """
50
+ Initialize client security manager.
51
+
52
+ Args:
53
+ config: Security configuration
54
+ """
55
+ if not SECURITY_FRAMEWORK_AVAILABLE:
56
+ raise ImportError("mcp_security_framework is not available")
57
+
58
+ self.config = config
59
+ self.security_config = config.get("security", {})
60
+
61
+ # Initialize SSL manager if needed
62
+ self.ssl_manager = None
63
+ if self.security_config.get("ssl", {}).get("enabled", False):
64
+ ssl_config = self._create_ssl_config()
65
+ self.ssl_manager = SSLManager(ssl_config)
66
+
67
+ logger.info("Client security manager initialized")
68
+
69
+ def _create_ssl_config(self) -> SSLConfig:
70
+ """Create SSL configuration for client connections."""
71
+ ssl_section = self.security_config.get("ssl", {})
72
+
73
+ return SSLConfig(
74
+ enabled=ssl_section.get("enabled", False),
75
+ cert_file=ssl_section.get("client_cert_file"),
76
+ key_file=ssl_section.get("client_key_file"),
77
+ ca_cert_file=ssl_section.get("ca_cert_file"),
78
+ verify_mode=ssl_section.get("verify_mode", "CERT_REQUIRED"),
79
+ min_tls_version=ssl_section.get("min_tls_version", "TLSv1.2"),
80
+ check_hostname=ssl_section.get("check_hostname", True),
81
+ check_expiry=ssl_section.get("check_expiry", True)
82
+ )
83
+
84
+ def create_client_ssl_context(self, server_hostname: Optional[str] = None) -> Optional[ssl.SSLContext]:
85
+ """
86
+ Create SSL context for client connections.
87
+
88
+ Args:
89
+ server_hostname: Server hostname for SNI
90
+
91
+ Returns:
92
+ SSL context or None if SSL not enabled
93
+ """
94
+ if not self.ssl_manager:
95
+ return None
96
+
97
+ try:
98
+ # Create client SSL context
99
+ context = self.ssl_manager.create_client_context()
100
+
101
+ if server_hostname:
102
+ # Set server hostname for SNI
103
+ context.check_hostname = True
104
+ context.verify_mode = ssl.CERT_REQUIRED
105
+
106
+ logger.info(f"Client SSL context created for {server_hostname or 'unknown server'}")
107
+ return context
108
+
109
+ except Exception as e:
110
+ logger.error(f"Failed to create client SSL context: {e}")
111
+ return None
112
+
113
+ def generate_client_api_key(self, user_id: str, prefix: str = "mcp_proxy") -> str:
114
+ """
115
+ Generate API key for client authentication.
116
+
117
+ Args:
118
+ user_id: User identifier
119
+ prefix: Key prefix
120
+
121
+ Returns:
122
+ Generated API key
123
+ """
124
+ try:
125
+ api_key = generate_api_key(prefix=prefix)
126
+ logger.info(f"Generated API key for user: {user_id}")
127
+ return api_key
128
+ except Exception as e:
129
+ logger.error(f"Failed to generate API key: {e}")
130
+ raise
131
+
132
+ def create_client_jwt_token(self, user_id: str, roles: List[str],
133
+ secret: str, algorithm: str = "HS256",
134
+ expiry_hours: int = 24) -> str:
135
+ """
136
+ Create JWT token for client authentication.
137
+
138
+ Args:
139
+ user_id: User identifier
140
+ roles: User roles
141
+ secret: JWT secret
142
+ algorithm: JWT algorithm
143
+ expiry_hours: Token expiry in hours
144
+
145
+ Returns:
146
+ JWT token
147
+ """
148
+ try:
149
+ payload = {
150
+ "user_id": user_id,
151
+ "roles": roles,
152
+ "type": "client_proxy"
153
+ }
154
+
155
+ token = create_jwt_token(
156
+ payload=payload,
157
+ secret=secret,
158
+ algorithm=algorithm,
159
+ expiry_hours=expiry_hours
160
+ )
161
+
162
+ logger.info(f"Created JWT token for user: {user_id}")
163
+ return token
164
+
165
+ except Exception as e:
166
+ logger.error(f"Failed to create JWT token: {e}")
167
+ raise
168
+
169
+ def validate_server_certificate(self, cert_path: str, ca_cert_path: Optional[str] = None) -> bool:
170
+ """
171
+ Validate server certificate before connection.
172
+
173
+ Args:
174
+ cert_path: Path to server certificate
175
+ ca_cert_path: Path to CA certificate
176
+
177
+ Returns:
178
+ True if certificate is valid
179
+ """
180
+ try:
181
+ # Validate certificate format
182
+ if not validate_certificate_format(cert_path):
183
+ logger.error(f"Invalid certificate format: {cert_path}")
184
+ return False
185
+
186
+ # Validate certificate chain if CA provided
187
+ if ca_cert_path:
188
+ if not validate_certificate_chain(cert_path, ca_cert_path):
189
+ logger.error(f"Invalid certificate chain: {cert_path}")
190
+ return False
191
+
192
+ # Parse certificate and check basic properties
193
+ cert_info = parse_certificate(cert_path)
194
+ if not cert_info:
195
+ logger.error(f"Failed to parse certificate: {cert_path}")
196
+ return False
197
+
198
+ logger.info(f"Server certificate validated: {cert_path}")
199
+ return True
200
+
201
+ except Exception as e:
202
+ logger.error(f"Failed to validate server certificate: {e}")
203
+ return False
204
+
205
+ def extract_server_roles(self, cert_path: str) -> List[str]:
206
+ """
207
+ Extract roles from server certificate.
208
+
209
+ Args:
210
+ cert_path: Path to server certificate
211
+
212
+ Returns:
213
+ List of roles extracted from certificate
214
+ """
215
+ try:
216
+ roles = extract_roles_from_certificate(cert_path)
217
+ logger.info(f"Extracted roles from server certificate: {roles}")
218
+ return roles
219
+ except Exception as e:
220
+ logger.error(f"Failed to extract roles from certificate: {e}")
221
+ return []
222
+
223
+ def get_client_auth_headers(self, auth_method: str = "api_key", **kwargs) -> Dict[str, str]:
224
+ """
225
+ Get authentication headers for client requests.
226
+
227
+ Args:
228
+ auth_method: Authentication method (api_key, jwt, certificate)
229
+ **kwargs: Additional parameters
230
+
231
+ Returns:
232
+ Dictionary of authentication headers
233
+ """
234
+ headers = {}
235
+
236
+ try:
237
+ if auth_method == "api_key":
238
+ api_key = kwargs.get("api_key")
239
+ if api_key:
240
+ headers["X-API-Key"] = api_key
241
+ headers["Authorization"] = f"Bearer {api_key}"
242
+
243
+ elif auth_method == "jwt":
244
+ token = kwargs.get("token")
245
+ if token:
246
+ headers["Authorization"] = f"Bearer {token}"
247
+
248
+ elif auth_method == "certificate":
249
+ # Certificate authentication is handled at SSL level
250
+ headers["X-Auth-Method"] = "certificate"
251
+
252
+ # Add common proxy headers
253
+ headers["X-Proxy-Type"] = "mcp_proxy_adapter"
254
+ headers["X-Client-Type"] = "proxy_client"
255
+
256
+ logger.debug(f"Created auth headers for method: {auth_method}")
257
+ return headers
258
+
259
+ except Exception as e:
260
+ logger.error(f"Failed to create auth headers: {e}")
261
+ return {}
262
+
263
+ def prepare_client_connection(self, server_config: Dict[str, Any]) -> Tuple[Optional[ssl.SSLContext], Dict[str, str]]:
264
+ """
265
+ Prepare secure client connection to server.
266
+
267
+ Args:
268
+ server_config: Server connection configuration
269
+
270
+ Returns:
271
+ Tuple of (SSL context, auth headers)
272
+ """
273
+ ssl_context = None
274
+ auth_headers = {}
275
+
276
+ try:
277
+ # Create SSL context if needed
278
+ if server_config.get("ssl", False):
279
+ server_hostname = server_config.get("hostname")
280
+ ssl_context = self.create_client_ssl_context(server_hostname)
281
+
282
+ # Create authentication headers
283
+ auth_method = server_config.get("auth_method", "api_key")
284
+ auth_headers = self.get_client_auth_headers(
285
+ auth_method=auth_method,
286
+ api_key=server_config.get("api_key"),
287
+ token=server_config.get("token")
288
+ )
289
+
290
+ logger.info(f"Prepared client connection for {server_config.get('hostname', 'unknown')}")
291
+ return ssl_context, auth_headers
292
+
293
+ except Exception as e:
294
+ logger.error(f"Failed to prepare client connection: {e}")
295
+ return None, {}
296
+
297
+ def validate_server_response(self, response_headers: Dict[str, str]) -> bool:
298
+ """
299
+ Validate server response for security compliance.
300
+
301
+ Args:
302
+ response_headers: Server response headers
303
+
304
+ Returns:
305
+ True if response is valid
306
+ """
307
+ try:
308
+ # Check for required security headers
309
+ required_headers = ["Content-Type"]
310
+ for header in required_headers:
311
+ if header not in response_headers:
312
+ logger.warning(f"Missing required header: {header}")
313
+
314
+ # Check for security headers
315
+ security_headers = ["X-Frame-Options", "X-Content-Type-Options"]
316
+ for header in security_headers:
317
+ if header in response_headers:
318
+ logger.debug(f"Found security header: {header}")
319
+
320
+ # Validate content type
321
+ content_type = response_headers.get("Content-Type", "")
322
+ if "application/json" not in content_type:
323
+ logger.warning(f"Unexpected content type: {content_type}")
324
+
325
+ return True
326
+
327
+ except Exception as e:
328
+ logger.error(f"Failed to validate server response: {e}")
329
+ return False
330
+
331
+ def get_client_certificate_info(self) -> Optional[Dict[str, Any]]:
332
+ """
333
+ Get information about client certificate.
334
+
335
+ Returns:
336
+ Certificate information or None
337
+ """
338
+ try:
339
+ ssl_config = self.security_config.get("ssl", {})
340
+ cert_path = ssl_config.get("client_cert_file")
341
+
342
+ if not cert_path or not Path(cert_path).exists():
343
+ return None
344
+
345
+ cert_info = parse_certificate(cert_path)
346
+ if cert_info:
347
+ roles = extract_roles_from_certificate(cert_path)
348
+ cert_info["roles"] = roles
349
+ return cert_info
350
+
351
+ return None
352
+
353
+ except Exception as e:
354
+ logger.error(f"Failed to get client certificate info: {e}")
355
+ return None
356
+
357
+ def is_ssl_enabled(self) -> bool:
358
+ """Check if SSL is enabled for client connections."""
359
+ return self.security_config.get("ssl", {}).get("enabled", False)
360
+
361
+ def get_supported_auth_methods(self) -> List[str]:
362
+ """Get list of supported authentication methods."""
363
+ return ["api_key", "jwt", "certificate"]
364
+
365
+
366
+ # Factory function for easy integration
367
+ def create_client_security_manager(config: Dict[str, Any]) -> Optional[ClientSecurityManager]:
368
+ """
369
+ Create client security manager instance.
370
+
371
+ Args:
372
+ config: Configuration dictionary
373
+
374
+ Returns:
375
+ ClientSecurityManager instance or None if framework not available
376
+ """
377
+ try:
378
+ return ClientSecurityManager(config)
379
+ except ImportError:
380
+ logger.warning("mcp_security_framework not available, client security disabled")
381
+ return None
382
+ except Exception as e:
383
+ logger.error(f"Failed to create client security manager: {e}")
384
+ return None
@@ -230,17 +230,22 @@ def setup_logging(
230
230
  return logger
231
231
 
232
232
 
233
- def _parse_file_size(size_str: str) -> int:
233
+ def _parse_file_size(size_str) -> int:
234
234
  """
235
235
  Parse file size string to bytes.
236
236
 
237
237
  Args:
238
- size_str: Size string (e.g., "10MB", "1GB", "100KB")
238
+ size_str: Size string (e.g., "10MB", "1GB", "100KB") or int
239
239
 
240
240
  Returns:
241
241
  Size in bytes
242
242
  """
243
- size_str = size_str.upper()
243
+ # If it's already an int, return it
244
+ if isinstance(size_str, int):
245
+ return size_str
246
+
247
+ # Convert to string and parse
248
+ size_str = str(size_str).upper()
244
249
  if size_str.endswith("KB"):
245
250
  return int(size_str[:-2]) * 1024
246
251
  elif size_str.endswith("MB"):