mcp-proxy-adapter 6.1.1__py3-none-any.whl → 6.2.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 (146) hide show
  1. mcp_proxy_adapter/__main__.py +27 -7
  2. mcp_proxy_adapter/api/app.py +18 -7
  3. mcp_proxy_adapter/commands/ssl_setup_command.py +234 -351
  4. mcp_proxy_adapter/core/app_factory.py +87 -3
  5. mcp_proxy_adapter/core/app_runner.py +272 -0
  6. mcp_proxy_adapter/core/certificate_utils.py +291 -73
  7. mcp_proxy_adapter/core/client.py +574 -0
  8. mcp_proxy_adapter/core/client_manager.py +284 -0
  9. mcp_proxy_adapter/core/server_adapter.py +17 -80
  10. mcp_proxy_adapter/core/server_engine.py +5 -99
  11. mcp_proxy_adapter/core/ssl_utils.py +13 -12
  12. mcp_proxy_adapter/core/transport_manager.py +5 -5
  13. mcp_proxy_adapter/examples/__init__.py +16 -0
  14. mcp_proxy_adapter/examples/basic_framework/__init__.py +7 -0
  15. mcp_proxy_adapter/examples/basic_framework/commands/__init__.py +4 -0
  16. mcp_proxy_adapter/examples/basic_framework/hooks/__init__.py +4 -0
  17. mcp_proxy_adapter/examples/basic_framework/main.py +21 -40
  18. mcp_proxy_adapter/examples/commands/__init__.py +5 -1
  19. mcp_proxy_adapter/examples/create_certificates_simple.py +260 -75
  20. mcp_proxy_adapter/examples/debug_request_state.py +4 -36
  21. mcp_proxy_adapter/examples/debug_role_chain.py +2 -49
  22. mcp_proxy_adapter/examples/demo_client.py +0 -66
  23. mcp_proxy_adapter/examples/full_application/__init__.py +11 -0
  24. mcp_proxy_adapter/examples/full_application/commands/__init__.py +7 -0
  25. mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +0 -19
  26. mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +0 -16
  27. mcp_proxy_adapter/examples/full_application/hooks/__init__.py +7 -0
  28. mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +0 -22
  29. mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +0 -24
  30. mcp_proxy_adapter/examples/full_application/main.py +65 -44
  31. mcp_proxy_adapter/examples/full_application/proxy_endpoints.py +154 -0
  32. mcp_proxy_adapter/examples/generate_all_certificates.py +0 -67
  33. mcp_proxy_adapter/examples/generate_certificates.py +0 -15
  34. mcp_proxy_adapter/examples/generate_certificates_and_tokens.py +369 -0
  35. mcp_proxy_adapter/examples/generate_test_configs.py +204 -0
  36. mcp_proxy_adapter/examples/proxy_registration_example.py +3 -70
  37. mcp_proxy_adapter/examples/run_example.py +1 -23
  38. mcp_proxy_adapter/examples/run_security_tests.py +2 -60
  39. mcp_proxy_adapter/examples/run_security_tests_fixed.py +0 -53
  40. mcp_proxy_adapter/examples/security_test_client.py +18 -123
  41. mcp_proxy_adapter/examples/setup_test_environment.py +179 -0
  42. mcp_proxy_adapter/examples/test_config.py +148 -0
  43. mcp_proxy_adapter/examples/test_config_generator.py +1 -25
  44. mcp_proxy_adapter/examples/test_examples.py +4 -67
  45. mcp_proxy_adapter/examples/universal_client.py +154 -162
  46. mcp_proxy_adapter/main.py +51 -161
  47. mcp_proxy_adapter/version.py +1 -1
  48. mcp_proxy_adapter-6.2.1.dist-info/METADATA +676 -0
  49. mcp_proxy_adapter-6.2.1.dist-info/RECORD +119 -0
  50. mcp_proxy_adapter/docs/EN/TROUBLESHOOTING.md +0 -285
  51. mcp_proxy_adapter/docs/RU/TROUBLESHOOTING.md +0 -285
  52. mcp_proxy_adapter/examples/README.md +0 -257
  53. mcp_proxy_adapter/examples/README_EN.md +0 -258
  54. mcp_proxy_adapter/examples/SECURITY_TESTING.md +0 -455
  55. mcp_proxy_adapter/examples/basic_framework/configs/http_auth.json +0 -37
  56. mcp_proxy_adapter/examples/basic_framework/configs/http_simple.json +0 -23
  57. mcp_proxy_adapter/examples/basic_framework/configs/https_auth.json +0 -43
  58. mcp_proxy_adapter/examples/basic_framework/configs/https_no_protocol_middleware.json +0 -36
  59. mcp_proxy_adapter/examples/basic_framework/configs/https_simple.json +0 -29
  60. mcp_proxy_adapter/examples/basic_framework/configs/mtls_no_protocol_middleware.json +0 -34
  61. mcp_proxy_adapter/examples/basic_framework/configs/mtls_no_roles.json +0 -39
  62. mcp_proxy_adapter/examples/basic_framework/configs/mtls_simple.json +0 -35
  63. mcp_proxy_adapter/examples/basic_framework/configs/mtls_with_roles.json +0 -45
  64. mcp_proxy_adapter/examples/basic_framework/roles.json +0 -21
  65. mcp_proxy_adapter/examples/cert_config.json +0 -9
  66. mcp_proxy_adapter/examples/certs/admin.crt +0 -32
  67. mcp_proxy_adapter/examples/certs/admin.key +0 -52
  68. mcp_proxy_adapter/examples/certs/admin_cert.pem +0 -21
  69. mcp_proxy_adapter/examples/certs/admin_key.pem +0 -28
  70. mcp_proxy_adapter/examples/certs/ca_cert.pem +0 -23
  71. mcp_proxy_adapter/examples/certs/ca_cert.srl +0 -1
  72. mcp_proxy_adapter/examples/certs/ca_key.pem +0 -28
  73. mcp_proxy_adapter/examples/certs/cert_config.json +0 -9
  74. mcp_proxy_adapter/examples/certs/client.crt +0 -32
  75. mcp_proxy_adapter/examples/certs/client.key +0 -52
  76. mcp_proxy_adapter/examples/certs/client_admin.crt +0 -32
  77. mcp_proxy_adapter/examples/certs/client_admin.key +0 -52
  78. mcp_proxy_adapter/examples/certs/client_user.crt +0 -32
  79. mcp_proxy_adapter/examples/certs/client_user.key +0 -52
  80. mcp_proxy_adapter/examples/certs/guest_cert.pem +0 -21
  81. mcp_proxy_adapter/examples/certs/guest_key.pem +0 -28
  82. mcp_proxy_adapter/examples/certs/mcp_proxy_adapter_ca_ca.crt +0 -23
  83. mcp_proxy_adapter/examples/certs/proxy_cert.pem +0 -21
  84. mcp_proxy_adapter/examples/certs/proxy_key.pem +0 -28
  85. mcp_proxy_adapter/examples/certs/readonly.crt +0 -32
  86. mcp_proxy_adapter/examples/certs/readonly.key +0 -52
  87. mcp_proxy_adapter/examples/certs/readonly_cert.pem +0 -21
  88. mcp_proxy_adapter/examples/certs/readonly_key.pem +0 -28
  89. mcp_proxy_adapter/examples/certs/server.crt +0 -32
  90. mcp_proxy_adapter/examples/certs/server.key +0 -52
  91. mcp_proxy_adapter/examples/certs/server_cert.pem +0 -32
  92. mcp_proxy_adapter/examples/certs/server_key.pem +0 -52
  93. mcp_proxy_adapter/examples/certs/test_ca_ca.crt +0 -20
  94. mcp_proxy_adapter/examples/certs/user.crt +0 -32
  95. mcp_proxy_adapter/examples/certs/user.key +0 -52
  96. mcp_proxy_adapter/examples/certs/user_cert.pem +0 -21
  97. mcp_proxy_adapter/examples/certs/user_key.pem +0 -28
  98. mcp_proxy_adapter/examples/client_configs/api_key_client.json +0 -13
  99. mcp_proxy_adapter/examples/client_configs/basic_auth_client.json +0 -13
  100. mcp_proxy_adapter/examples/client_configs/certificate_client.json +0 -22
  101. mcp_proxy_adapter/examples/client_configs/jwt_client.json +0 -15
  102. mcp_proxy_adapter/examples/client_configs/no_auth_client.json +0 -9
  103. mcp_proxy_adapter/examples/full_application/configs/http_auth.json +0 -37
  104. mcp_proxy_adapter/examples/full_application/configs/http_simple.json +0 -23
  105. mcp_proxy_adapter/examples/full_application/configs/https_auth.json +0 -39
  106. mcp_proxy_adapter/examples/full_application/configs/https_simple.json +0 -25
  107. mcp_proxy_adapter/examples/full_application/configs/mtls_no_roles.json +0 -39
  108. mcp_proxy_adapter/examples/full_application/configs/mtls_with_roles.json +0 -45
  109. mcp_proxy_adapter/examples/full_application/roles.json +0 -21
  110. mcp_proxy_adapter/examples/keys/ca_key.pem +0 -28
  111. mcp_proxy_adapter/examples/keys/mcp_proxy_adapter_ca_ca.key +0 -28
  112. mcp_proxy_adapter/examples/keys/test_ca_ca.key +0 -28
  113. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log +0 -220
  114. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.1 +0 -1
  115. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.2 +0 -1
  116. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.3 +0 -1
  117. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.4 +0 -1
  118. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.5 +0 -1
  119. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log +0 -220
  120. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.1 +0 -1
  121. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.2 +0 -1
  122. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.3 +0 -1
  123. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.4 +0 -1
  124. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.5 +0 -1
  125. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log +0 -2
  126. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.1 +0 -1
  127. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.2 +0 -1
  128. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.3 +0 -1
  129. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.4 +0 -1
  130. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.5 +0 -1
  131. mcp_proxy_adapter/examples/roles.json +0 -38
  132. mcp_proxy_adapter/examples/server_configs/config_basic_http.json +0 -204
  133. mcp_proxy_adapter/examples/server_configs/config_http_token.json +0 -238
  134. mcp_proxy_adapter/examples/server_configs/config_https.json +0 -215
  135. mcp_proxy_adapter/examples/server_configs/config_https_token.json +0 -231
  136. mcp_proxy_adapter/examples/server_configs/config_mtls.json +0 -215
  137. mcp_proxy_adapter/examples/server_configs/config_proxy_registration.json +0 -250
  138. mcp_proxy_adapter/examples/server_configs/config_simple.json +0 -46
  139. mcp_proxy_adapter/examples/server_configs/roles.json +0 -38
  140. mcp_proxy_adapter/utils/config_generator.py +0 -727
  141. mcp_proxy_adapter-6.1.1.dist-info/METADATA +0 -205
  142. mcp_proxy_adapter-6.1.1.dist-info/RECORD +0 -197
  143. mcp_proxy_adapter-6.1.1.dist-info/entry_points.txt +0 -2
  144. mcp_proxy_adapter-6.1.1.dist-info/licenses/LICENSE +0 -21
  145. {mcp_proxy_adapter-6.1.1.dist-info → mcp_proxy_adapter-6.2.1.dist-info}/WHEEL +0 -0
  146. {mcp_proxy_adapter-6.1.1.dist-info → mcp_proxy_adapter-6.2.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,574 @@
1
+ """
2
+ Universal Client for MCP Proxy Adapter Framework
3
+
4
+ This module provides a universal client that can connect to MCP Proxy Adapter servers
5
+ using various authentication methods and protocols. It's designed to be used for
6
+ proxy registration and general API communication.
7
+
8
+ Author: Vasiliy Zdanovskiy
9
+ email: vasilyvz@gmail.com
10
+ """
11
+
12
+ import asyncio
13
+ import json
14
+ import os
15
+ import ssl
16
+ import time
17
+ from typing import Dict, Any, Optional, List, Union
18
+ from urllib.parse import urljoin
19
+ from pathlib import Path
20
+
21
+ import aiohttp
22
+ import requests
23
+ from requests.exceptions import RequestException
24
+
25
+ # Import security framework components
26
+ try:
27
+ from mcp_security_framework import SecurityManager, AuthManager, CertificateManager
28
+ from mcp_security_framework.utils import generate_api_key, create_jwt_token, validate_jwt_token
29
+ from mcp_security_framework.utils import extract_roles_from_cert, validate_certificate_chain
30
+ from mcp_security_framework.utils import create_ssl_context, validate_server_certificate
31
+ SECURITY_FRAMEWORK_AVAILABLE = True
32
+ except ImportError:
33
+ SECURITY_FRAMEWORK_AVAILABLE = False
34
+
35
+
36
+ class UniversalClient:
37
+ """
38
+ Universal client that demonstrates all possible secure connection methods.
39
+
40
+ Supports:
41
+ - HTTP/HTTPS connections
42
+ - API Key authentication
43
+ - JWT token authentication
44
+ - Certificate-based authentication
45
+ - SSL/TLS with custom certificates
46
+ - Role-based access control
47
+ - Rate limiting awareness
48
+ """
49
+
50
+ def __init__(self, config: Dict[str, Any]):
51
+ """
52
+ Initialize universal client with configuration.
53
+
54
+ Args:
55
+ config: Client configuration with security settings
56
+ """
57
+ self.config = config
58
+ self.base_url = config.get("server_url", "http://localhost:8000")
59
+ self.timeout = config.get("timeout", 30)
60
+ self.retry_attempts = config.get("retry_attempts", 3)
61
+ self.retry_delay = config.get("retry_delay", 1)
62
+
63
+ # Security configuration
64
+ self.security_config = config.get("security", {})
65
+ self.auth_method = self.security_config.get("auth_method", "none")
66
+
67
+ # Initialize security managers if framework is available
68
+ self.security_manager = None
69
+ self.auth_manager = None
70
+ self.cert_manager = None
71
+
72
+ if SECURITY_FRAMEWORK_AVAILABLE:
73
+ self._initialize_security_managers()
74
+
75
+ # Session management
76
+ self.session: Optional[aiohttp.ClientSession] = None
77
+ self.current_token: Optional[str] = None
78
+ self.token_expiry: Optional[float] = None
79
+
80
+ print(f"Universal client initialized with auth method: {self.auth_method}")
81
+
82
+ def _initialize_security_managers(self) -> None:
83
+ """Initialize security framework managers."""
84
+ try:
85
+ # Initialize security manager
86
+ self.security_manager = SecurityManager(self.security_config)
87
+
88
+ # Initialize auth manager
89
+ auth_config = self.security_config.get("auth", {})
90
+ self.auth_manager = AuthManager(auth_config)
91
+
92
+ # Initialize certificate manager
93
+ cert_config = self.security_config.get("certificates", {})
94
+ self.cert_manager = CertificateManager(cert_config)
95
+
96
+ print("Security framework managers initialized successfully")
97
+ except Exception as e:
98
+ print(f"Warning: Failed to initialize security managers: {e}")
99
+
100
+ async def __aenter__(self):
101
+ """Async context manager entry."""
102
+ await self.connect()
103
+ return self
104
+
105
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
106
+ """Async context manager exit."""
107
+ await self.disconnect()
108
+
109
+ async def connect(self) -> None:
110
+ """Establish connection with authentication."""
111
+ print(f"Connecting to {self.base_url} with {self.auth_method} authentication...")
112
+
113
+ # Create SSL context
114
+ ssl_context = self._create_ssl_context()
115
+
116
+ # Create connector with SSL context
117
+ connector = None
118
+ if ssl_context:
119
+ connector = aiohttp.TCPConnector(ssl=ssl_context)
120
+
121
+ # Create session
122
+ self.session = aiohttp.ClientSession(connector=connector)
123
+
124
+ # Perform authentication based on method
125
+ if self.auth_method == "api_key":
126
+ await self._authenticate_api_key()
127
+ elif self.auth_method == "jwt":
128
+ await self._authenticate_jwt()
129
+ elif self.auth_method == "certificate":
130
+ await self._authenticate_certificate()
131
+ elif self.auth_method == "basic":
132
+ await self._authenticate_basic()
133
+ else:
134
+ print("No authentication required")
135
+
136
+ print("Connection established successfully")
137
+
138
+ async def disconnect(self) -> None:
139
+ """Close connection and cleanup."""
140
+ if self.session:
141
+ await self.session.close()
142
+ self.session = None
143
+ print("Connection closed")
144
+
145
+ async def _authenticate_api_key(self) -> None:
146
+ """Authenticate using API key."""
147
+ api_key_config = self.security_config.get("api_key", {})
148
+ api_key = api_key_config.get("key")
149
+
150
+ if not api_key:
151
+ raise ValueError("API key not provided in configuration")
152
+
153
+ # Store API key for requests
154
+ self.current_token = api_key
155
+ print(f"Authenticated with API key: {api_key[:8]}...")
156
+
157
+ async def _authenticate_jwt(self) -> None:
158
+ """Authenticate using JWT token."""
159
+ jwt_config = self.security_config.get("jwt", {})
160
+
161
+ # Check if we have a stored token that's still valid
162
+ if self.current_token and self.token_expiry and time.time() < self.token_expiry:
163
+ print("Using existing JWT token")
164
+ return
165
+
166
+ # Get credentials for JWT
167
+ username = jwt_config.get("username")
168
+ password = jwt_config.get("password")
169
+ secret = jwt_config.get("secret")
170
+
171
+ if not all([username, password, secret]):
172
+ raise ValueError("JWT credentials not provided in configuration")
173
+
174
+ # Create JWT token
175
+ if SECURITY_FRAMEWORK_AVAILABLE:
176
+ self.current_token = create_jwt_token(
177
+ username,
178
+ secret,
179
+ expiry_hours=jwt_config.get("expiry_hours", 24)
180
+ )
181
+ else:
182
+ # Simple JWT creation (for demonstration)
183
+ import jwt
184
+ payload = {
185
+ "username": username,
186
+ "exp": time.time() + (jwt_config.get("expiry_hours", 24) * 3600)
187
+ }
188
+ self.current_token = jwt.encode(payload, secret, algorithm="HS256")
189
+
190
+ self.token_expiry = time.time() + (jwt_config.get("expiry_hours", 24) * 3600)
191
+ print(f"Authenticated with JWT token: {self.current_token[:20]}...")
192
+
193
+ async def _authenticate_certificate(self) -> None:
194
+ """Authenticate using client certificate."""
195
+ cert_config = self.security_config.get("certificate", {})
196
+
197
+ cert_file = cert_config.get("cert_file")
198
+ key_file = cert_config.get("key_file")
199
+
200
+ if not cert_file or not key_file:
201
+ raise ValueError("Certificate files not provided in configuration")
202
+
203
+ # Validate certificate
204
+ if SECURITY_FRAMEWORK_AVAILABLE and self.cert_manager:
205
+ try:
206
+ cert_info = self.cert_manager.validate_certificate(cert_file, key_file)
207
+ print(f"Certificate validated: {cert_info.get('subject', 'Unknown')}")
208
+
209
+ # Extract roles from certificate
210
+ roles = extract_roles_from_cert(cert_file)
211
+ if roles:
212
+ print(f"Certificate roles: {roles}")
213
+ except Exception as e:
214
+ print(f"Warning: Certificate validation failed: {e}")
215
+
216
+ print("Certificate authentication prepared")
217
+
218
+ async def _authenticate_basic(self) -> None:
219
+ """Authenticate using basic authentication."""
220
+ basic_config = self.security_config.get("basic", {})
221
+ username = basic_config.get("username")
222
+ password = basic_config.get("password")
223
+
224
+ if not username or not password:
225
+ raise ValueError("Basic auth credentials not provided in configuration")
226
+
227
+ import base64
228
+ credentials = base64.b64encode(f"{username}:{password}".encode()).decode()
229
+ self.current_token = f"Basic {credentials}"
230
+ print(f"Authenticated with basic auth: {username}")
231
+
232
+ def _get_auth_headers(self) -> Dict[str, str]:
233
+ """Get authentication headers for requests."""
234
+ headers = {"Content-Type": "application/json"}
235
+
236
+ if not self.current_token:
237
+ return headers
238
+
239
+ if self.auth_method == "api_key":
240
+ api_key_config = self.security_config.get("api_key", {})
241
+ header_name = api_key_config.get("header", "X-API-Key")
242
+ headers[header_name] = self.current_token
243
+ elif self.auth_method == "jwt":
244
+ headers["Authorization"] = f"Bearer {self.current_token}"
245
+ elif self.auth_method == "basic":
246
+ headers["Authorization"] = self.current_token
247
+
248
+ return headers
249
+
250
+ def _create_ssl_context(self) -> Optional[ssl.SSLContext]:
251
+ """Create SSL context for secure connections."""
252
+ ssl_config = self.security_config.get("ssl", {})
253
+ if not ssl_config.get("enabled", False):
254
+ return None
255
+
256
+ try:
257
+ context: Optional[ssl.SSLContext] = None
258
+
259
+ # Try security framework first
260
+ if self.security_manager:
261
+ try:
262
+ context = self.security_manager.create_client_ssl_context()
263
+ except Exception:
264
+ context = None
265
+
266
+ # Fallback SSL context creation
267
+ if context is None:
268
+ context = ssl.create_default_context()
269
+
270
+ # Always honor explicit client certificate config for mTLS
271
+ cert_config = self.security_config.get("certificate", {})
272
+ if cert_config.get("enabled", False):
273
+ cert_file = cert_config.get("cert_file")
274
+ key_file = cert_config.get("key_file")
275
+ if cert_file and key_file:
276
+ context.load_cert_chain(certfile=cert_file, keyfile=key_file)
277
+
278
+ # Add CA certificate if provided
279
+ ca_cert_file = ssl_config.get("ca_cert_file") or ssl_config.get("ca_cert")
280
+ if ca_cert_file and os.path.exists(ca_cert_file):
281
+ context.load_verify_locations(cafile=ca_cert_file)
282
+
283
+ # Configure verification
284
+ if ssl_config.get("check_hostname", True):
285
+ context.check_hostname = True
286
+ context.verify_mode = ssl.CERT_REQUIRED
287
+ else:
288
+ context.check_hostname = False
289
+ context.verify_mode = ssl.CERT_NONE
290
+
291
+ return context
292
+ except Exception as e:
293
+ print(f"Warning: Failed to create SSL context: {e}")
294
+ return None
295
+
296
+ async def request(
297
+ self,
298
+ method: str,
299
+ endpoint: str,
300
+ data: Optional[Dict[str, Any]] = None,
301
+ headers: Optional[Dict[str, str]] = None
302
+ ) -> Dict[str, Any]:
303
+ """
304
+ Make authenticated request to server.
305
+
306
+ Args:
307
+ method: HTTP method (GET, POST, etc.)
308
+ endpoint: API endpoint
309
+ data: Request data
310
+ headers: Additional headers
311
+
312
+ Returns:
313
+ Response data
314
+ """
315
+ url = urljoin(self.base_url, endpoint)
316
+
317
+ # Prepare headers
318
+ request_headers = self._get_auth_headers()
319
+ if headers:
320
+ request_headers.update(headers)
321
+
322
+ try:
323
+ for attempt in range(self.retry_attempts):
324
+ try:
325
+ async with self.session.request(
326
+ method,
327
+ url,
328
+ json=data,
329
+ headers=request_headers,
330
+ timeout=aiohttp.ClientTimeout(total=self.timeout)
331
+ ) as response:
332
+ result = await response.json()
333
+
334
+ # Validate response if security framework available
335
+ if SECURITY_FRAMEWORK_AVAILABLE and self.security_manager:
336
+ self.security_manager.validate_server_response(dict(response.headers))
337
+
338
+ if response.status >= 400:
339
+ print(f"Request failed with status {response.status}: {result}")
340
+ return {"error": result, "status": response.status}
341
+
342
+ return result
343
+
344
+ except Exception as e:
345
+ print(f"Request attempt {attempt + 1} failed: {e}")
346
+ if attempt < self.retry_attempts - 1:
347
+ await asyncio.sleep(self.retry_delay)
348
+ else:
349
+ raise
350
+ except Exception as e:
351
+ print(f"Request failed: {e}")
352
+ raise
353
+
354
+ async def get(self, endpoint: str, **kwargs) -> Dict[str, Any]:
355
+ """Make GET request."""
356
+ return await self.request("GET", endpoint, **kwargs)
357
+
358
+ async def post(self, endpoint: str, data: Dict[str, Any], **kwargs) -> Dict[str, Any]:
359
+ """Make POST request."""
360
+ return await self.request("POST", endpoint, data=data, **kwargs)
361
+
362
+ async def put(self, endpoint: str, data: Dict[str, Any], **kwargs) -> Dict[str, Any]:
363
+ """Make PUT request."""
364
+ return await self.request("PUT", endpoint, data=data, **kwargs)
365
+
366
+ async def delete(self, endpoint: str, **kwargs) -> Dict[str, Any]:
367
+ """Make DELETE request."""
368
+ return await self.request("DELETE", endpoint, **kwargs)
369
+
370
+ async def test_connection(self) -> bool:
371
+ """Test connection to server."""
372
+ try:
373
+ result = await self.get("/health")
374
+ if "error" not in result:
375
+ print("✅ Connection test successful")
376
+ return True
377
+ else:
378
+ print(f"❌ Connection test failed: {result}")
379
+ return False
380
+ except Exception as e:
381
+ print(f"❌ Connection test failed: {e}")
382
+ return False
383
+
384
+ async def test_security_features(self) -> Dict[str, bool]:
385
+ """Test various security features."""
386
+ results = {}
387
+
388
+ # Test basic connectivity
389
+ results["connectivity"] = await self.test_connection()
390
+
391
+ # Test authentication
392
+ if self.auth_method != "none":
393
+ try:
394
+ result = await self.get("/api/auth/status")
395
+ results["authentication"] = "error" not in result
396
+ except:
397
+ results["authentication"] = False
398
+
399
+ # Test SSL/TLS
400
+ if self.base_url.startswith("https"):
401
+ results["ssl_tls"] = True
402
+ else:
403
+ results["ssl_tls"] = False
404
+
405
+ return results
406
+
407
+ async def register_proxy(self, proxy_config: Dict[str, Any]) -> Dict[str, Any]:
408
+ """
409
+ Register with proxy server.
410
+
411
+ Args:
412
+ proxy_config: Proxy registration configuration
413
+
414
+ Returns:
415
+ Registration result
416
+ """
417
+ try:
418
+ result = await self.post("/api/jsonrpc", {
419
+ "jsonrpc": "2.0",
420
+ "method": "proxy_registration",
421
+ "params": proxy_config,
422
+ "id": 1
423
+ })
424
+ return result
425
+ except Exception as e:
426
+ print(f"Proxy registration failed: {e}")
427
+ return {"error": str(e)}
428
+
429
+ async def execute_command(self, command: str, params: Dict[str, Any] = None) -> Dict[str, Any]:
430
+ """
431
+ Execute a command on the server.
432
+
433
+ Args:
434
+ command: Command name
435
+ params: Command parameters
436
+
437
+ Returns:
438
+ Command result
439
+ """
440
+ try:
441
+ result = await self.post("/api/jsonrpc", {
442
+ "jsonrpc": "2.0",
443
+ "method": command,
444
+ "params": params or {},
445
+ "id": 1
446
+ })
447
+ return result
448
+ except Exception as e:
449
+ print(f"Command execution failed: {e}")
450
+ return {"error": str(e)}
451
+
452
+
453
+ def create_client_from_config(config_file: str) -> UniversalClient:
454
+ """
455
+ Create a UniversalClient instance from a configuration file.
456
+
457
+ Args:
458
+ config_file: Path to configuration file
459
+
460
+ Returns:
461
+ UniversalClient instance
462
+ """
463
+ try:
464
+ with open(config_file, 'r') as f:
465
+ config_data = json.load(f)
466
+
467
+ # Extract server configuration
468
+ server_config = config_data.get("server", {})
469
+ host = server_config.get("host", "127.0.0.1")
470
+ port = server_config.get("port", 8000)
471
+
472
+ # Determine protocol
473
+ ssl_config = config_data.get("ssl", {})
474
+ ssl_enabled = ssl_config.get("enabled", False)
475
+ protocol = "https" if ssl_enabled else "http"
476
+
477
+ server_url = f"{protocol}://{host}:{port}"
478
+
479
+ # Create client configuration
480
+ client_config = {
481
+ "server_url": server_url,
482
+ "timeout": 30,
483
+ "retry_attempts": 3,
484
+ "retry_delay": 1,
485
+ "security": {
486
+ "auth_method": "none"
487
+ }
488
+ }
489
+
490
+ # Add SSL configuration if needed
491
+ if ssl_enabled:
492
+ client_config["security"]["ssl"] = {
493
+ "enabled": True,
494
+ "check_hostname": False,
495
+ "verify": False
496
+ }
497
+
498
+ # Add CA certificate if available
499
+ ca_cert = ssl_config.get("ca_cert")
500
+ if ca_cert and os.path.exists(ca_cert):
501
+ client_config["security"]["ssl"]["ca_cert_file"] = ca_cert
502
+
503
+ return UniversalClient(client_config)
504
+
505
+ except Exception as e:
506
+ raise ValueError(f"Failed to create client from config: {e}")
507
+
508
+
509
+ # CLI interface for standalone usage
510
+ async def main():
511
+ """Main function for CLI usage."""
512
+ import argparse
513
+
514
+ parser = argparse.ArgumentParser(description="Universal Client for MCP Proxy Adapter")
515
+ parser.add_argument("--config", help="Path to configuration file")
516
+ parser.add_argument("--method", help="JSON-RPC method to call")
517
+ parser.add_argument("--params", help="JSON-RPC parameters (JSON string)")
518
+ parser.add_argument("--auth-method", help="Authentication method")
519
+ parser.add_argument("--server-url", help="Server URL")
520
+
521
+ args = parser.parse_args()
522
+
523
+ if args.config:
524
+ # Load configuration from file
525
+ try:
526
+ client = create_client_from_config(args.config)
527
+
528
+ print(f"🚀 Testing --config connection")
529
+ print("=" * 40)
530
+ print(f"Universal client initialized with auth method: --config")
531
+ print(f"Connecting to {client.base_url} with --config authentication...")
532
+
533
+ async with client:
534
+ # Test connection
535
+ success = await client.test_connection()
536
+
537
+ if success:
538
+ print("No authentication required")
539
+ print("Connection established successfully")
540
+
541
+ if args.method:
542
+ # Execute JSON-RPC method
543
+ params = {}
544
+ if args.params:
545
+ try:
546
+ params = json.loads(args.params)
547
+ except json.JSONDecodeError:
548
+ print("❌ Invalid JSON parameters")
549
+ return
550
+
551
+ result = await client.execute_command(args.method, params)
552
+ print(f"✅ Method '{args.method}' executed successfully:")
553
+ print(json.dumps(result, indent=2))
554
+ else:
555
+ # Default to help command
556
+ result = await client.execute_command("help")
557
+ print("✅ Help command executed successfully:")
558
+ print(json.dumps(result, indent=2))
559
+ else:
560
+ print("❌ Connection failed")
561
+ print("Connection closed")
562
+
563
+ except FileNotFoundError:
564
+ print(f"❌ Configuration file not found: {args.config}")
565
+ except json.JSONDecodeError:
566
+ print(f"❌ Invalid JSON in configuration file: {args.config}")
567
+ except Exception as e:
568
+ print(f"❌ Error loading configuration: {e}")
569
+ else:
570
+ print("❌ Configuration file required. Use --config option.")
571
+
572
+
573
+ if __name__ == "__main__":
574
+ asyncio.run(main())