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