mcp-proxy-adapter 6.1.1__py3-none-any.whl → 6.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) 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.0.dist-info/METADATA +687 -0
  49. mcp_proxy_adapter-6.2.0.dist-info/RECORD +122 -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-6.1.1.dist-info/METADATA +0 -205
  141. mcp_proxy_adapter-6.1.1.dist-info/RECORD +0 -197
  142. {mcp_proxy_adapter-6.1.1.dist-info → mcp_proxy_adapter-6.2.0.dist-info}/WHEEL +0 -0
  143. {mcp_proxy_adapter-6.1.1.dist-info → mcp_proxy_adapter-6.2.0.dist-info}/entry_points.txt +0 -0
  144. {mcp_proxy_adapter-6.1.1.dist-info → mcp_proxy_adapter-6.2.0.dist-info}/licenses/LICENSE +0 -0
  145. {mcp_proxy_adapter-6.1.1.dist-info → mcp_proxy_adapter-6.2.0.dist-info}/top_level.txt +0 -0
@@ -1,26 +1,22 @@
1
1
  """
2
2
  Universal Client Example
3
-
4
3
  This module demonstrates all possible secure connection methods to the server
5
4
  using the mcp_security_framework. The client supports all authentication methods
6
5
  and connection types supported by the security framework.
7
-
8
6
  Author: Vasiliy Zdanovskiy
9
7
  email: vasilyvz@gmail.com
10
8
  """
11
-
12
9
  import asyncio
13
10
  import json
11
+ import os
14
12
  import ssl
15
13
  import time
16
14
  from typing import Dict, Any, Optional, List, Union
17
15
  from urllib.parse import urljoin
18
16
  from pathlib import Path
19
-
20
17
  import aiohttp
21
18
  import requests
22
19
  from requests.exceptions import RequestException
23
-
24
20
  # Import security framework components
25
21
  try:
26
22
  from mcp_security_framework import SecurityManager, AuthManager, CertificateManager
@@ -31,12 +27,9 @@ try:
31
27
  except ImportError:
32
28
  SECURITY_FRAMEWORK_AVAILABLE = False
33
29
  print("Warning: mcp_security_framework not available. Using basic HTTP client.")
34
-
35
-
36
30
  class UniversalClient:
37
31
  """
38
32
  Universal client that demonstrates all possible secure connection methods.
39
-
40
33
  Supports:
41
34
  - HTTP/HTTPS connections
42
35
  - API Key authentication
@@ -46,11 +39,9 @@ class UniversalClient:
46
39
  - Role-based access control
47
40
  - Rate limiting awareness
48
41
  """
49
-
50
42
  def __init__(self, config: Dict[str, Any]):
51
43
  """
52
44
  Initialize universal client with configuration.
53
-
54
45
  Args:
55
46
  config: Client configuration with security settings
56
47
  """
@@ -59,57 +50,52 @@ class UniversalClient:
59
50
  self.timeout = config.get("timeout", 30)
60
51
  self.retry_attempts = config.get("retry_attempts", 3)
61
52
  self.retry_delay = config.get("retry_delay", 1)
62
-
63
53
  # Security configuration
64
54
  self.security_config = config.get("security", {})
65
55
  self.auth_method = self.security_config.get("auth_method", "none")
66
-
67
56
  # Initialize security managers if framework is available
68
57
  self.security_manager = None
69
58
  self.auth_manager = None
70
59
  self.cert_manager = None
71
-
72
60
  if SECURITY_FRAMEWORK_AVAILABLE:
73
61
  self._initialize_security_managers()
74
-
75
62
  # Session management
76
63
  self.session: Optional[aiohttp.ClientSession] = None
77
64
  self.current_token: Optional[str] = None
78
65
  self.token_expiry: Optional[float] = None
79
-
80
66
  print(f"Universal client initialized with auth method: {self.auth_method}")
81
-
82
67
  def _initialize_security_managers(self) -> None:
83
68
  """Initialize security framework managers."""
84
69
  try:
85
70
  # Initialize security manager
86
71
  self.security_manager = SecurityManager(self.security_config)
87
-
88
72
  # Initialize auth manager
89
73
  auth_config = self.security_config.get("auth", {})
90
74
  self.auth_manager = AuthManager(auth_config)
91
-
92
75
  # Initialize certificate manager
93
76
  cert_config = self.security_config.get("certificates", {})
94
77
  self.cert_manager = CertificateManager(cert_config)
95
-
96
78
  print("Security framework managers initialized successfully")
97
79
  except Exception as e:
98
80
  print(f"Warning: Failed to initialize security managers: {e}")
99
-
100
81
  async def __aenter__(self):
101
82
  """Async context manager entry."""
102
83
  await self.connect()
103
84
  return self
104
-
105
85
  async def __aexit__(self, exc_type, exc_val, exc_tb):
106
86
  """Async context manager exit."""
107
87
  await self.disconnect()
108
-
109
88
  async def connect(self) -> None:
110
89
  """Establish connection with authentication."""
111
90
  print(f"Connecting to {self.base_url} with {self.auth_method} authentication...")
112
-
91
+ # Create SSL context
92
+ ssl_context = self._create_ssl_context()
93
+ # Create connector with SSL context
94
+ connector = None
95
+ if ssl_context:
96
+ connector = aiohttp.TCPConnector(ssl=ssl_context)
97
+ # Create session
98
+ self.session = aiohttp.ClientSession(connector=connector)
113
99
  # Perform authentication based on method
114
100
  if self.auth_method == "api_key":
115
101
  await self._authenticate_api_key()
@@ -121,50 +107,40 @@ class UniversalClient:
121
107
  await self._authenticate_basic()
122
108
  else:
123
109
  print("No authentication required")
124
-
125
110
  print("Connection established successfully")
126
-
127
111
  async def disconnect(self) -> None:
128
112
  """Close connection and cleanup."""
129
113
  if self.session:
130
114
  await self.session.close()
131
115
  self.session = None
132
116
  print("Connection closed")
133
-
134
117
  async def _authenticate_api_key(self) -> None:
135
118
  """Authenticate using API key."""
136
119
  api_key_config = self.security_config.get("api_key", {})
137
120
  api_key = api_key_config.get("key")
138
-
139
121
  if not api_key:
140
122
  raise ValueError("API key not provided in configuration")
141
-
142
123
  # Store API key for requests
143
124
  self.current_token = api_key
144
125
  print(f"Authenticated with API key: {api_key[:8]}...")
145
-
146
126
  async def _authenticate_jwt(self) -> None:
147
127
  """Authenticate using JWT token."""
148
128
  jwt_config = self.security_config.get("jwt", {})
149
-
150
129
  # Check if we have a stored token that's still valid
151
130
  if self.current_token and self.token_expiry and time.time() < self.token_expiry:
152
131
  print("Using existing JWT token")
153
132
  return
154
-
155
133
  # Get credentials for JWT
156
134
  username = jwt_config.get("username")
157
135
  password = jwt_config.get("password")
158
136
  secret = jwt_config.get("secret")
159
-
160
137
  if not all([username, password, secret]):
161
138
  raise ValueError("JWT credentials not provided in configuration")
162
-
163
139
  # Create JWT token
164
140
  if SECURITY_FRAMEWORK_AVAILABLE:
165
141
  self.current_token = create_jwt_token(
166
- username,
167
- secret,
142
+ username,
143
+ secret,
168
144
  expiry_hours=jwt_config.get("expiry_hours", 24)
169
145
  )
170
146
  else:
@@ -175,56 +151,43 @@ class UniversalClient:
175
151
  "exp": time.time() + (jwt_config.get("expiry_hours", 24) * 3600)
176
152
  }
177
153
  self.current_token = jwt.encode(payload, secret, algorithm="HS256")
178
-
179
154
  self.token_expiry = time.time() + (jwt_config.get("expiry_hours", 24) * 3600)
180
155
  print(f"Authenticated with JWT token: {self.current_token[:20]}...")
181
-
182
156
  async def _authenticate_certificate(self) -> None:
183
157
  """Authenticate using client certificate."""
184
158
  cert_config = self.security_config.get("certificate", {})
185
-
186
159
  cert_file = cert_config.get("cert_file")
187
160
  key_file = cert_config.get("key_file")
188
-
189
161
  if not cert_file or not key_file:
190
162
  raise ValueError("Certificate files not provided in configuration")
191
-
192
163
  # Validate certificate
193
164
  if SECURITY_FRAMEWORK_AVAILABLE and self.cert_manager:
194
165
  try:
195
166
  cert_info = self.cert_manager.validate_certificate(cert_file, key_file)
196
167
  print(f"Certificate validated: {cert_info.get('subject', 'Unknown')}")
197
-
198
168
  # Extract roles from certificate
199
169
  roles = extract_roles_from_cert(cert_file)
200
170
  if roles:
201
171
  print(f"Certificate roles: {roles}")
202
172
  except Exception as e:
203
173
  print(f"Warning: Certificate validation failed: {e}")
204
-
205
174
  print("Certificate authentication prepared")
206
-
207
175
  async def _authenticate_basic(self) -> None:
208
176
  """Authenticate using basic authentication."""
209
177
  basic_config = self.security_config.get("basic", {})
210
178
  username = basic_config.get("username")
211
179
  password = basic_config.get("password")
212
-
213
180
  if not username or not password:
214
181
  raise ValueError("Basic auth credentials not provided in configuration")
215
-
216
182
  import base64
217
183
  credentials = base64.b64encode(f"{username}:{password}".encode()).decode()
218
184
  self.current_token = f"Basic {credentials}"
219
185
  print(f"Authenticated with basic auth: {username}")
220
-
221
186
  def _get_auth_headers(self) -> Dict[str, str]:
222
187
  """Get authentication headers for requests."""
223
188
  headers = {"Content-Type": "application/json"}
224
-
225
189
  if not self.current_token:
226
190
  return headers
227
-
228
191
  if self.auth_method == "api_key":
229
192
  api_key_config = self.security_config.get("api_key", {})
230
193
  header_name = api_key_config.get("header", "X-API-Key")
@@ -233,27 +196,21 @@ class UniversalClient:
233
196
  headers["Authorization"] = f"Bearer {self.current_token}"
234
197
  elif self.auth_method == "basic":
235
198
  headers["Authorization"] = self.current_token
236
-
237
199
  return headers
238
-
239
200
  def _create_ssl_context(self) -> Optional[ssl.SSLContext]:
240
201
  """Create SSL context for secure connections."""
241
- if not SECURITY_FRAMEWORK_AVAILABLE:
202
+ ssl_config = self.security_config.get("ssl", {})
203
+ if not ssl_config.get("enabled", False):
242
204
  return None
243
-
244
205
  try:
245
206
  ssl_config = self.security_config.get("ssl", {})
246
-
247
207
  if not ssl_config.get("enabled", False):
248
208
  return None
249
-
250
209
  # Create SSL context using security framework
251
210
  if self.security_manager:
252
211
  return self.security_manager.create_client_ssl_context()
253
-
254
212
  # Fallback SSL context creation
255
213
  context = ssl.create_default_context()
256
-
257
214
  # Add client certificate if provided
258
215
  cert_config = self.security_config.get("certificate", {})
259
216
  if cert_config.get("enabled", False):
@@ -261,12 +218,10 @@ class UniversalClient:
261
218
  key_file = cert_config.get("key_file")
262
219
  if cert_file and key_file:
263
220
  context.load_cert_chain(cert_file, key_file)
264
-
265
221
  # Add CA certificate if provided
266
- ca_cert_file = ssl_config.get("ca_cert_file")
267
- if ca_cert_file:
222
+ ca_cert_file = ssl_config.get("ca_cert_file") or ssl_config.get("ca_cert")
223
+ if ca_cert_file and os.path.exists(ca_cert_file):
268
224
  context.load_verify_locations(ca_cert_file)
269
-
270
225
  # Configure verification
271
226
  if ssl_config.get("check_hostname", True):
272
227
  context.check_hostname = True
@@ -274,95 +229,71 @@ class UniversalClient:
274
229
  else:
275
230
  context.check_hostname = False
276
231
  context.verify_mode = ssl.CERT_NONE
277
-
278
232
  return context
279
233
  except Exception as e:
280
234
  print(f"Warning: Failed to create SSL context: {e}")
281
235
  return None
282
-
283
236
  async def request(
284
- self,
285
- method: str,
286
- endpoint: str,
237
+ self,
238
+ method: str,
239
+ endpoint: str,
287
240
  data: Optional[Dict[str, Any]] = None,
288
241
  headers: Optional[Dict[str, str]] = None
289
242
  ) -> Dict[str, Any]:
290
243
  """
291
244
  Make authenticated request to server.
292
-
293
245
  Args:
294
246
  method: HTTP method (GET, POST, etc.)
295
247
  endpoint: API endpoint
296
248
  data: Request data
297
249
  headers: Additional headers
298
-
299
250
  Returns:
300
251
  Response data
301
252
  """
302
253
  url = urljoin(self.base_url, endpoint)
303
-
304
254
  # Prepare headers
305
255
  request_headers = self._get_auth_headers()
306
256
  if headers:
307
257
  request_headers.update(headers)
308
-
309
- # Create SSL context
310
- ssl_context = self._create_ssl_context()
311
-
312
- # Create connector with SSL context
313
- connector = None
314
- if ssl_context:
315
- connector = aiohttp.TCPConnector(ssl=ssl_context)
316
-
317
258
  try:
318
- async with aiohttp.ClientSession(connector=connector) as session:
319
- for attempt in range(self.retry_attempts):
320
- try:
321
- async with session.request(
322
- method,
323
- url,
324
- json=data,
325
- headers=request_headers,
326
- timeout=aiohttp.ClientTimeout(total=self.timeout)
327
- ) as response:
328
- result = await response.json()
329
-
330
- # Validate response if security framework available
331
- if SECURITY_FRAMEWORK_AVAILABLE and self.security_manager:
332
- self.security_manager.validate_server_response(dict(response.headers))
333
-
334
- if response.status >= 400:
335
- print(f"Request failed with status {response.status}: {result}")
336
- return {"error": result, "status": response.status}
337
-
338
- return result
339
-
340
- except Exception as e:
341
- print(f"Request attempt {attempt + 1} failed: {e}")
342
- if attempt < self.retry_attempts - 1:
343
- await asyncio.sleep(self.retry_delay)
344
- else:
345
- raise
346
- finally:
347
- if connector:
348
- await connector.close()
349
-
259
+ for attempt in range(self.retry_attempts):
260
+ try:
261
+ async with self.session.request(
262
+ method,
263
+ url,
264
+ json=data,
265
+ headers=request_headers,
266
+ timeout=aiohttp.ClientTimeout(total=self.timeout)
267
+ ) as response:
268
+ result = await response.json()
269
+ # Validate response if security framework available
270
+ if SECURITY_FRAMEWORK_AVAILABLE and self.security_manager:
271
+ self.security_manager.validate_server_response(dict(response.headers))
272
+ if response.status >= 400:
273
+ print(f"Request failed with status {response.status}: {result}")
274
+ return {"error": result, "status": response.status}
275
+ return result
276
+ except Exception as e:
277
+ print(f"Request attempt {attempt + 1} failed: {e}")
278
+ if attempt < self.retry_attempts - 1:
279
+ await asyncio.sleep(self.retry_delay)
280
+ else:
281
+ raise
282
+ except Exception as e:
283
+ print(f"Request failed: {e}")
284
+ raise
350
285
  async def get(self, endpoint: str, **kwargs) -> Dict[str, Any]:
351
286
  """Make GET request."""
352
287
  return await self.request("GET", endpoint, **kwargs)
353
-
354
288
  async def post(self, endpoint: str, data: Dict[str, Any], **kwargs) -> Dict[str, Any]:
355
289
  """Make POST request."""
356
290
  return await self.request("POST", endpoint, data=data, **kwargs)
357
-
358
291
  async def put(self, endpoint: str, data: Dict[str, Any], **kwargs) -> Dict[str, Any]:
359
292
  """Make PUT request."""
360
293
  return await self.request("PUT", endpoint, data=data, **kwargs)
361
-
362
294
  async def delete(self, endpoint: str, **kwargs) -> Dict[str, Any]:
363
295
  """Make DELETE request."""
364
296
  return await self.request("DELETE", endpoint, **kwargs)
365
-
366
297
  async def test_connection(self) -> bool:
367
298
  """Test connection to server."""
368
299
  try:
@@ -376,14 +307,11 @@ class UniversalClient:
376
307
  except Exception as e:
377
308
  print(f"❌ Connection test failed: {e}")
378
309
  return False
379
-
380
310
  async def test_security_features(self) -> Dict[str, bool]:
381
311
  """Test various security features."""
382
312
  results = {}
383
-
384
313
  # Test basic connectivity
385
314
  results["connectivity"] = await self.test_connection()
386
-
387
315
  # Test authentication
388
316
  if self.auth_method != "none":
389
317
  try:
@@ -391,22 +319,17 @@ class UniversalClient:
391
319
  results["authentication"] = "error" not in result
392
320
  except:
393
321
  results["authentication"] = False
394
-
395
322
  # Test SSL/TLS
396
323
  if self.base_url.startswith("https"):
397
324
  results["ssl_tls"] = True
398
325
  else:
399
326
  results["ssl_tls"] = False
400
-
401
327
  # Test certificate validation
402
328
  if self.auth_method == "certificate" and SECURITY_FRAMEWORK_AVAILABLE:
403
329
  results["certificate_validation"] = True
404
330
  else:
405
331
  results["certificate_validation"] = False
406
-
407
332
  return results
408
-
409
-
410
333
  def create_client_config(
411
334
  server_url: str,
412
335
  auth_method: str = "none",
@@ -414,12 +337,10 @@ def create_client_config(
414
337
  ) -> Dict[str, Any]:
415
338
  """
416
339
  Create client configuration for different authentication methods.
417
-
418
340
  Args:
419
341
  server_url: Server URL
420
342
  auth_method: Authentication method (none, api_key, jwt, certificate, basic)
421
343
  **kwargs: Additional configuration parameters
422
-
423
344
  Returns:
424
345
  Client configuration dictionary
425
346
  """
@@ -432,13 +353,11 @@ def create_client_config(
432
353
  "auth_method": auth_method
433
354
  }
434
355
  }
435
-
436
356
  if auth_method == "api_key":
437
357
  config["security"]["api_key"] = {
438
358
  "key": kwargs.get("api_key", "your_api_key_here"),
439
359
  "header": kwargs.get("header", "X-API-Key")
440
360
  }
441
-
442
361
  elif auth_method == "jwt":
443
362
  config["security"]["jwt"] = {
444
363
  "username": kwargs.get("username", "user"),
@@ -446,7 +365,6 @@ def create_client_config(
446
365
  "secret": kwargs.get("secret", "your_jwt_secret"),
447
366
  "expiry_hours": kwargs.get("expiry_hours", 24)
448
367
  }
449
-
450
368
  elif auth_method == "certificate":
451
369
  config["security"]["certificate"] = {
452
370
  "enabled": True,
@@ -459,21 +377,16 @@ def create_client_config(
459
377
  "check_hostname": kwargs.get("check_hostname", True),
460
378
  "ca_cert_file": kwargs.get("ca_cert_file", "./certs/ca.crt")
461
379
  }
462
-
463
380
  elif auth_method == "basic":
464
381
  config["security"]["basic"] = {
465
382
  "username": kwargs.get("username", "user"),
466
383
  "password": kwargs.get("password", "password")
467
384
  }
468
-
469
385
  return config
470
-
471
-
472
386
  async def demo_all_connection_methods():
473
387
  """Demonstrate all possible connection methods."""
474
388
  print("🚀 Universal Client Demo - All Connection Methods")
475
389
  print("=" * 60)
476
-
477
390
  # Test configurations for different auth methods
478
391
  test_configs = [
479
392
  {
@@ -483,16 +396,16 @@ async def demo_all_connection_methods():
483
396
  {
484
397
  "name": "API Key Authentication",
485
398
  "config": create_client_config(
486
- "http://localhost:8000",
487
- "api_key",
399
+ "http://localhost:8000",
400
+ "api_key",
488
401
  api_key="demo_api_key_123"
489
402
  )
490
403
  },
491
404
  {
492
405
  "name": "JWT Authentication",
493
406
  "config": create_client_config(
494
- "http://localhost:8000",
495
- "jwt",
407
+ "http://localhost:8000",
408
+ "jwt",
496
409
  username="demo_user",
497
410
  password="demo_password",
498
411
  secret="demo_jwt_secret"
@@ -501,8 +414,8 @@ async def demo_all_connection_methods():
501
414
  {
502
415
  "name": "Basic Authentication",
503
416
  "config": create_client_config(
504
- "http://localhost:8000",
505
- "basic",
417
+ "http://localhost:8000",
418
+ "basic",
506
419
  username="demo_user",
507
420
  password="demo_password"
508
421
  )
@@ -510,24 +423,21 @@ async def demo_all_connection_methods():
510
423
  {
511
424
  "name": "Certificate Authentication (HTTPS)",
512
425
  "config": create_client_config(
513
- "https://localhost:8443",
514
- "certificate",
426
+ "https://localhost:8443",
427
+ "certificate",
515
428
  cert_file="./certs/client.crt",
516
429
  key_file="./keys/client.key",
517
430
  ca_cert_file="./certs/ca.crt"
518
431
  )
519
432
  }
520
433
  ]
521
-
522
434
  for test_config in test_configs:
523
435
  print(f"\n📋 Testing: {test_config['name']}")
524
436
  print("-" * 40)
525
-
526
437
  try:
527
438
  async with UniversalClient(test_config["config"]) as client:
528
439
  # Test connection
529
440
  success = await client.test_connection()
530
-
531
441
  if success:
532
442
  # Test security features
533
443
  security_results = await client.test_security_features()
@@ -535,7 +445,6 @@ async def demo_all_connection_methods():
535
445
  for feature, status in security_results.items():
536
446
  status_icon = "✅" if status else "❌"
537
447
  print(f" {status_icon} {feature}: {status}")
538
-
539
448
  # Make a test API call
540
449
  try:
541
450
  result = await client.get("/api/status")
@@ -544,39 +453,29 @@ async def demo_all_connection_methods():
544
453
  print(f"API call failed: {e}")
545
454
  else:
546
455
  print("❌ Connection failed")
547
-
548
456
  except Exception as e:
549
457
  print(f"❌ Test failed: {e}")
550
-
551
458
  print("\n🎉 Demo completed!")
552
-
553
-
554
459
  async def demo_specific_connection(auth_method: str, **kwargs):
555
460
  """
556
461
  Demo specific connection method.
557
-
558
462
  Args:
559
463
  auth_method: Authentication method to test
560
464
  **kwargs: Configuration parameters
561
465
  """
562
466
  print(f"🚀 Testing {auth_method} connection")
563
467
  print("=" * 40)
564
-
565
468
  config = create_client_config("http://localhost:8000", auth_method, **kwargs)
566
-
567
469
  async with UniversalClient(config) as client:
568
470
  # Test connection
569
471
  success = await client.test_connection()
570
-
571
472
  if success:
572
473
  print("✅ Connection successful!")
573
-
574
474
  # Make some API calls
575
475
  try:
576
476
  # Get server status
577
477
  status = await client.get("/api/status")
578
478
  print(f"Server Status: {status}")
579
-
580
479
  # Test command execution
581
480
  command_data = {
582
481
  "jsonrpc": "2.0",
@@ -584,24 +483,118 @@ async def demo_specific_connection(auth_method: str, **kwargs):
584
483
  "params": {"message": "Hello from universal client!"},
585
484
  "id": 1
586
485
  }
587
-
588
486
  result = await client.post("/api/jsonrpc", command_data)
589
487
  print(f"Command Result: {result}")
590
-
591
488
  except Exception as e:
592
489
  print(f"API calls failed: {e}")
593
490
  else:
594
491
  print("❌ Connection failed")
595
-
596
-
597
492
  if __name__ == "__main__":
598
493
  import sys
599
-
600
- if len(sys.argv) > 1:
601
- # Test specific auth method
494
+ import argparse
495
+ import json
496
+ parser = argparse.ArgumentParser(description="Universal Client for MCP Proxy Adapter")
497
+ parser.add_argument("--config", help="Path to configuration file")
498
+ parser.add_argument("--method", help="JSON-RPC method to call")
499
+ parser.add_argument("--params", help="JSON-RPC parameters (JSON string)")
500
+ parser.add_argument("--auth-method", help="Authentication method")
501
+ parser.add_argument("--server-url", help="Server URL")
502
+ args = parser.parse_args()
503
+ if args.config:
504
+ # Load configuration from file
505
+ try:
506
+ with open(args.config, 'r') as f:
507
+ config_data = json.load(f)
508
+ # Extract server configuration
509
+ server_config = config_data.get("server", {})
510
+ host = server_config.get("host", "127.0.0.1")
511
+ port = server_config.get("port", 8000)
512
+ # Determine protocol
513
+ ssl_config = config_data.get("ssl", {})
514
+ ssl_enabled = ssl_config.get("enabled", False)
515
+ protocol = "https" if ssl_enabled else "http"
516
+ server_url = f"{protocol}://{host}:{port}"
517
+ print(f"🚀 Testing --config connection")
518
+ print("=" * 40)
519
+ print(f"Universal client initialized with auth method: --config")
520
+ print(f"Connecting to {server_url} with --config authentication...")
521
+ # Create client configuration
522
+ client_config = {
523
+ "server_url": server_url,
524
+ "timeout": 30,
525
+ "retry_attempts": 3,
526
+ "retry_delay": 1,
527
+ "security": {
528
+ "auth_method": "none"
529
+ }
530
+ }
531
+ # Add SSL configuration if needed
532
+ if ssl_enabled:
533
+ client_config["security"]["ssl"] = {
534
+ "enabled": True,
535
+ "check_hostname": False,
536
+ "verify": False
537
+ }
538
+ # Add CA certificate if available
539
+ ca_cert = ssl_config.get("ca_cert")
540
+ if ca_cert and os.path.exists(ca_cert):
541
+ client_config["security"]["ssl"]["ca_cert_file"] = ca_cert
542
+ async def test_config_connection():
543
+ async with UniversalClient(client_config) as client:
544
+ # Test connection
545
+ success = await client.test_connection()
546
+ if success:
547
+ print("No authentication required")
548
+ print("Connection established successfully")
549
+ if args.method:
550
+ # Execute JSON-RPC method
551
+ params = {}
552
+ if args.params:
553
+ try:
554
+ params = json.loads(args.params)
555
+ except json.JSONDecodeError:
556
+ print("❌ Invalid JSON parameters")
557
+ return
558
+ command_data = {
559
+ "jsonrpc": "2.0",
560
+ "method": args.method,
561
+ "params": params,
562
+ "id": 1
563
+ }
564
+ try:
565
+ result = await client.post("/api/jsonrpc", command_data)
566
+ print(f"✅ Method '{args.method}' executed successfully:")
567
+ print(json.dumps(result, indent=2))
568
+ except Exception as e:
569
+ print(f"❌ Method execution failed: {e}")
570
+ else:
571
+ # Default to help command
572
+ command_data = {
573
+ "jsonrpc": "2.0",
574
+ "method": "help",
575
+ "params": {},
576
+ "id": 1
577
+ }
578
+ try:
579
+ result = await client.post("/api/jsonrpc", command_data)
580
+ print("✅ Help command executed successfully:")
581
+ print(json.dumps(result, indent=2))
582
+ except Exception as e:
583
+ print(f"❌ Help command failed: {e}")
584
+ else:
585
+ print("❌ Connection failed")
586
+ print("Connection closed")
587
+ asyncio.run(test_config_connection())
588
+ except FileNotFoundError:
589
+ print(f"❌ Configuration file not found: {args.config}")
590
+ except json.JSONDecodeError:
591
+ print(f"❌ Invalid JSON in configuration file: {args.config}")
592
+ except Exception as e:
593
+ print(f"❌ Error loading configuration: {e}")
594
+ elif len(sys.argv) > 1:
595
+ # Test specific auth method (legacy mode)
602
596
  auth_method = sys.argv[1]
603
597
  kwargs = {}
604
-
605
598
  if auth_method == "api_key":
606
599
  kwargs["api_key"] = "demo_key_123"
607
600
  elif auth_method == "jwt":
@@ -621,7 +614,6 @@ if __name__ == "__main__":
621
614
  "username": "demo_user",
622
615
  "password": "demo_password"
623
616
  })
624
-
625
617
  asyncio.run(demo_specific_connection(auth_method, **kwargs))
626
618
  else:
627
619
  # Demo all connection methods