mcp-proxy-adapter 4.1.1__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 (200) hide show
  1. mcp_proxy_adapter/__main__.py +32 -0
  2. mcp_proxy_adapter/api/app.py +290 -33
  3. mcp_proxy_adapter/api/handlers.py +32 -6
  4. mcp_proxy_adapter/api/middleware/__init__.py +38 -32
  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 +201 -0
  10. mcp_proxy_adapter/api/middleware/transport_middleware.py +122 -0
  11. mcp_proxy_adapter/api/middleware/unified_security.py +197 -0
  12. mcp_proxy_adapter/api/middleware/user_info_middleware.py +158 -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 +8 -1
  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 +366 -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 +394 -14
  41. mcp_proxy_adapter/core/app_factory.py +410 -0
  42. mcp_proxy_adapter/core/app_runner.py +272 -0
  43. mcp_proxy_adapter/core/auth_validator.py +606 -0
  44. mcp_proxy_adapter/core/certificate_utils.py +1045 -0
  45. mcp_proxy_adapter/core/client.py +574 -0
  46. mcp_proxy_adapter/core/client_manager.py +284 -0
  47. mcp_proxy_adapter/core/client_security.py +384 -0
  48. mcp_proxy_adapter/core/config_converter.py +405 -0
  49. mcp_proxy_adapter/core/config_validator.py +218 -0
  50. mcp_proxy_adapter/core/logging.py +19 -3
  51. mcp_proxy_adapter/core/mtls_asgi.py +156 -0
  52. mcp_proxy_adapter/core/mtls_asgi_app.py +187 -0
  53. mcp_proxy_adapter/core/protocol_manager.py +385 -0
  54. mcp_proxy_adapter/core/proxy_client.py +602 -0
  55. mcp_proxy_adapter/core/proxy_registration.py +522 -0
  56. mcp_proxy_adapter/core/role_utils.py +426 -0
  57. mcp_proxy_adapter/core/security_adapter.py +370 -0
  58. mcp_proxy_adapter/core/security_factory.py +239 -0
  59. mcp_proxy_adapter/core/security_integration.py +286 -0
  60. mcp_proxy_adapter/core/server_adapter.py +282 -0
  61. mcp_proxy_adapter/core/server_engine.py +270 -0
  62. mcp_proxy_adapter/core/settings.py +1 -0
  63. mcp_proxy_adapter/core/ssl_utils.py +234 -0
  64. mcp_proxy_adapter/core/transport_manager.py +292 -0
  65. mcp_proxy_adapter/core/unified_config_adapter.py +579 -0
  66. mcp_proxy_adapter/custom_openapi.py +22 -11
  67. mcp_proxy_adapter/examples/__init__.py +13 -4
  68. mcp_proxy_adapter/examples/basic_framework/__init__.py +9 -0
  69. mcp_proxy_adapter/examples/basic_framework/commands/__init__.py +4 -0
  70. mcp_proxy_adapter/examples/basic_framework/hooks/__init__.py +4 -0
  71. mcp_proxy_adapter/examples/basic_framework/main.py +44 -0
  72. mcp_proxy_adapter/examples/commands/__init__.py +5 -0
  73. mcp_proxy_adapter/examples/create_certificates_simple.py +550 -0
  74. mcp_proxy_adapter/examples/debug_request_state.py +112 -0
  75. mcp_proxy_adapter/examples/debug_role_chain.py +158 -0
  76. mcp_proxy_adapter/examples/demo_client.py +275 -0
  77. mcp_proxy_adapter/examples/examples/basic_framework/__init__.py +9 -0
  78. mcp_proxy_adapter/examples/examples/basic_framework/commands/__init__.py +4 -0
  79. mcp_proxy_adapter/examples/examples/basic_framework/hooks/__init__.py +4 -0
  80. mcp_proxy_adapter/examples/examples/basic_framework/main.py +44 -0
  81. mcp_proxy_adapter/examples/examples/full_application/__init__.py +12 -0
  82. mcp_proxy_adapter/examples/examples/full_application/commands/__init__.py +7 -0
  83. mcp_proxy_adapter/examples/examples/full_application/commands/custom_echo_command.py +80 -0
  84. mcp_proxy_adapter/examples/examples/full_application/commands/dynamic_calculator_command.py +90 -0
  85. mcp_proxy_adapter/examples/examples/full_application/hooks/__init__.py +7 -0
  86. mcp_proxy_adapter/examples/examples/full_application/hooks/application_hooks.py +75 -0
  87. mcp_proxy_adapter/examples/examples/full_application/hooks/builtin_command_hooks.py +71 -0
  88. mcp_proxy_adapter/examples/examples/full_application/main.py +173 -0
  89. mcp_proxy_adapter/examples/examples/full_application/proxy_endpoints.py +154 -0
  90. mcp_proxy_adapter/examples/full_application/__init__.py +12 -0
  91. mcp_proxy_adapter/examples/full_application/commands/__init__.py +7 -0
  92. mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +80 -0
  93. mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +90 -0
  94. mcp_proxy_adapter/examples/full_application/hooks/__init__.py +7 -0
  95. mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +75 -0
  96. mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +71 -0
  97. mcp_proxy_adapter/examples/full_application/main.py +173 -0
  98. mcp_proxy_adapter/examples/full_application/proxy_endpoints.py +154 -0
  99. mcp_proxy_adapter/examples/generate_all_certificates.py +362 -0
  100. mcp_proxy_adapter/examples/generate_certificates.py +177 -0
  101. mcp_proxy_adapter/examples/generate_certificates_and_tokens.py +369 -0
  102. mcp_proxy_adapter/examples/generate_test_configs.py +331 -0
  103. mcp_proxy_adapter/examples/proxy_registration_example.py +334 -0
  104. mcp_proxy_adapter/examples/run_example.py +59 -0
  105. mcp_proxy_adapter/examples/run_full_test_suite.py +318 -0
  106. mcp_proxy_adapter/examples/run_proxy_server.py +146 -0
  107. mcp_proxy_adapter/examples/run_security_tests.py +544 -0
  108. mcp_proxy_adapter/examples/run_security_tests_fixed.py +247 -0
  109. mcp_proxy_adapter/examples/scripts/config_generator.py +740 -0
  110. mcp_proxy_adapter/examples/scripts/create_certificates_simple.py +560 -0
  111. mcp_proxy_adapter/examples/scripts/generate_certificates_and_tokens.py +369 -0
  112. mcp_proxy_adapter/examples/security_test_client.py +782 -0
  113. mcp_proxy_adapter/examples/setup_test_environment.py +328 -0
  114. mcp_proxy_adapter/examples/test_config.py +148 -0
  115. mcp_proxy_adapter/examples/test_config_generator.py +86 -0
  116. mcp_proxy_adapter/examples/test_examples.py +281 -0
  117. mcp_proxy_adapter/examples/universal_client.py +620 -0
  118. mcp_proxy_adapter/main.py +93 -0
  119. mcp_proxy_adapter/utils/config_generator.py +1008 -0
  120. mcp_proxy_adapter/version.py +5 -2
  121. mcp_proxy_adapter-6.0.1.dist-info/METADATA +679 -0
  122. mcp_proxy_adapter-6.0.1.dist-info/RECORD +140 -0
  123. mcp_proxy_adapter-6.0.1.dist-info/entry_points.txt +2 -0
  124. {mcp_proxy_adapter-4.1.1.dist-info → mcp_proxy_adapter-6.0.1.dist-info}/licenses/LICENSE +2 -2
  125. mcp_proxy_adapter/api/middleware/auth.py +0 -146
  126. mcp_proxy_adapter/api/middleware/rate_limit.py +0 -152
  127. mcp_proxy_adapter/commands/reload_settings_command.py +0 -125
  128. mcp_proxy_adapter/examples/README.md +0 -124
  129. mcp_proxy_adapter/examples/basic_server/README.md +0 -60
  130. mcp_proxy_adapter/examples/basic_server/__init__.py +0 -7
  131. mcp_proxy_adapter/examples/basic_server/basic_custom_settings.json +0 -39
  132. mcp_proxy_adapter/examples/basic_server/config.json +0 -35
  133. mcp_proxy_adapter/examples/basic_server/custom_settings_example.py +0 -238
  134. mcp_proxy_adapter/examples/basic_server/server.py +0 -103
  135. mcp_proxy_adapter/examples/custom_commands/README.md +0 -127
  136. mcp_proxy_adapter/examples/custom_commands/__init__.py +0 -27
  137. mcp_proxy_adapter/examples/custom_commands/advanced_hooks.py +0 -250
  138. mcp_proxy_adapter/examples/custom_commands/auto_commands/__init__.py +0 -6
  139. mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_echo_command.py +0 -103
  140. mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_info_command.py +0 -111
  141. mcp_proxy_adapter/examples/custom_commands/config.json +0 -35
  142. mcp_proxy_adapter/examples/custom_commands/custom_health_command.py +0 -169
  143. mcp_proxy_adapter/examples/custom_commands/custom_help_command.py +0 -215
  144. mcp_proxy_adapter/examples/custom_commands/custom_openapi_generator.py +0 -76
  145. mcp_proxy_adapter/examples/custom_commands/custom_settings.json +0 -96
  146. mcp_proxy_adapter/examples/custom_commands/custom_settings_manager.py +0 -241
  147. mcp_proxy_adapter/examples/custom_commands/data_transform_command.py +0 -135
  148. mcp_proxy_adapter/examples/custom_commands/echo_command.py +0 -122
  149. mcp_proxy_adapter/examples/custom_commands/hooks.py +0 -230
  150. mcp_proxy_adapter/examples/custom_commands/intercept_command.py +0 -123
  151. mcp_proxy_adapter/examples/custom_commands/manual_echo_command.py +0 -103
  152. mcp_proxy_adapter/examples/custom_commands/server.py +0 -228
  153. mcp_proxy_adapter/examples/custom_commands/test_hooks.py +0 -176
  154. mcp_proxy_adapter/examples/deployment/README.md +0 -49
  155. mcp_proxy_adapter/examples/deployment/__init__.py +0 -7
  156. mcp_proxy_adapter/examples/deployment/config.development.json +0 -8
  157. mcp_proxy_adapter/examples/deployment/config.json +0 -29
  158. mcp_proxy_adapter/examples/deployment/config.production.json +0 -12
  159. mcp_proxy_adapter/examples/deployment/config.staging.json +0 -11
  160. mcp_proxy_adapter/examples/deployment/docker-compose.yml +0 -31
  161. mcp_proxy_adapter/examples/deployment/run.sh +0 -43
  162. mcp_proxy_adapter/examples/deployment/run_docker.sh +0 -84
  163. mcp_proxy_adapter/schemas/base_schema.json +0 -114
  164. mcp_proxy_adapter/schemas/openapi_schema.json +0 -314
  165. mcp_proxy_adapter/tests/__init__.py +0 -0
  166. mcp_proxy_adapter/tests/api/__init__.py +0 -3
  167. mcp_proxy_adapter/tests/api/test_cmd_endpoint.py +0 -115
  168. mcp_proxy_adapter/tests/api/test_custom_openapi.py +0 -617
  169. mcp_proxy_adapter/tests/api/test_handlers.py +0 -522
  170. mcp_proxy_adapter/tests/api/test_middleware.py +0 -340
  171. mcp_proxy_adapter/tests/api/test_schemas.py +0 -546
  172. mcp_proxy_adapter/tests/api/test_tool_integration.py +0 -531
  173. mcp_proxy_adapter/tests/commands/__init__.py +0 -3
  174. mcp_proxy_adapter/tests/commands/test_config_command.py +0 -211
  175. mcp_proxy_adapter/tests/commands/test_echo_command.py +0 -127
  176. mcp_proxy_adapter/tests/commands/test_help_command.py +0 -136
  177. mcp_proxy_adapter/tests/conftest.py +0 -131
  178. mcp_proxy_adapter/tests/functional/__init__.py +0 -3
  179. mcp_proxy_adapter/tests/functional/test_api.py +0 -253
  180. mcp_proxy_adapter/tests/integration/__init__.py +0 -3
  181. mcp_proxy_adapter/tests/integration/test_cmd_integration.py +0 -129
  182. mcp_proxy_adapter/tests/integration/test_integration.py +0 -255
  183. mcp_proxy_adapter/tests/performance/__init__.py +0 -3
  184. mcp_proxy_adapter/tests/performance/test_performance.py +0 -189
  185. mcp_proxy_adapter/tests/stubs/__init__.py +0 -10
  186. mcp_proxy_adapter/tests/stubs/echo_command.py +0 -104
  187. mcp_proxy_adapter/tests/test_api_endpoints.py +0 -271
  188. mcp_proxy_adapter/tests/test_api_handlers.py +0 -289
  189. mcp_proxy_adapter/tests/test_base_command.py +0 -123
  190. mcp_proxy_adapter/tests/test_batch_requests.py +0 -117
  191. mcp_proxy_adapter/tests/test_command_registry.py +0 -281
  192. mcp_proxy_adapter/tests/test_config.py +0 -127
  193. mcp_proxy_adapter/tests/test_utils.py +0 -65
  194. mcp_proxy_adapter/tests/unit/__init__.py +0 -3
  195. mcp_proxy_adapter/tests/unit/test_base_command.py +0 -436
  196. mcp_proxy_adapter/tests/unit/test_config.py +0 -217
  197. mcp_proxy_adapter-4.1.1.dist-info/METADATA +0 -200
  198. mcp_proxy_adapter-4.1.1.dist-info/RECORD +0 -110
  199. {mcp_proxy_adapter-4.1.1.dist-info → mcp_proxy_adapter-6.0.1.dist-info}/WHEEL +0 -0
  200. {mcp_proxy_adapter-4.1.1.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