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.
- mcp_proxy_adapter/__main__.py +27 -7
- mcp_proxy_adapter/api/app.py +18 -7
- mcp_proxy_adapter/commands/ssl_setup_command.py +234 -351
- mcp_proxy_adapter/core/app_factory.py +87 -3
- mcp_proxy_adapter/core/app_runner.py +272 -0
- mcp_proxy_adapter/core/certificate_utils.py +291 -73
- mcp_proxy_adapter/core/client.py +574 -0
- mcp_proxy_adapter/core/client_manager.py +284 -0
- mcp_proxy_adapter/core/server_adapter.py +17 -80
- mcp_proxy_adapter/core/server_engine.py +5 -99
- mcp_proxy_adapter/core/ssl_utils.py +13 -12
- mcp_proxy_adapter/core/transport_manager.py +5 -5
- mcp_proxy_adapter/examples/__init__.py +16 -0
- mcp_proxy_adapter/examples/basic_framework/__init__.py +7 -0
- mcp_proxy_adapter/examples/basic_framework/commands/__init__.py +4 -0
- mcp_proxy_adapter/examples/basic_framework/hooks/__init__.py +4 -0
- mcp_proxy_adapter/examples/basic_framework/main.py +21 -40
- mcp_proxy_adapter/examples/commands/__init__.py +5 -1
- mcp_proxy_adapter/examples/create_certificates_simple.py +260 -75
- mcp_proxy_adapter/examples/debug_request_state.py +4 -36
- mcp_proxy_adapter/examples/debug_role_chain.py +2 -49
- mcp_proxy_adapter/examples/demo_client.py +0 -66
- mcp_proxy_adapter/examples/full_application/__init__.py +11 -0
- mcp_proxy_adapter/examples/full_application/commands/__init__.py +7 -0
- mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +0 -19
- mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +0 -16
- mcp_proxy_adapter/examples/full_application/hooks/__init__.py +7 -0
- mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +0 -22
- mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +0 -24
- mcp_proxy_adapter/examples/full_application/main.py +65 -44
- mcp_proxy_adapter/examples/full_application/proxy_endpoints.py +154 -0
- mcp_proxy_adapter/examples/generate_all_certificates.py +0 -67
- mcp_proxy_adapter/examples/generate_certificates.py +0 -15
- mcp_proxy_adapter/examples/generate_certificates_and_tokens.py +369 -0
- mcp_proxy_adapter/examples/generate_test_configs.py +204 -0
- mcp_proxy_adapter/examples/proxy_registration_example.py +3 -70
- mcp_proxy_adapter/examples/run_example.py +1 -23
- mcp_proxy_adapter/examples/run_security_tests.py +2 -60
- mcp_proxy_adapter/examples/run_security_tests_fixed.py +0 -53
- mcp_proxy_adapter/examples/security_test_client.py +18 -123
- mcp_proxy_adapter/examples/setup_test_environment.py +179 -0
- mcp_proxy_adapter/examples/test_config.py +148 -0
- mcp_proxy_adapter/examples/test_config_generator.py +1 -25
- mcp_proxy_adapter/examples/test_examples.py +4 -67
- mcp_proxy_adapter/examples/universal_client.py +154 -162
- mcp_proxy_adapter/main.py +51 -161
- mcp_proxy_adapter/version.py +1 -1
- mcp_proxy_adapter-6.2.0.dist-info/METADATA +687 -0
- mcp_proxy_adapter-6.2.0.dist-info/RECORD +122 -0
- mcp_proxy_adapter/docs/EN/TROUBLESHOOTING.md +0 -285
- mcp_proxy_adapter/docs/RU/TROUBLESHOOTING.md +0 -285
- mcp_proxy_adapter/examples/README.md +0 -257
- mcp_proxy_adapter/examples/README_EN.md +0 -258
- mcp_proxy_adapter/examples/SECURITY_TESTING.md +0 -455
- mcp_proxy_adapter/examples/basic_framework/configs/http_auth.json +0 -37
- mcp_proxy_adapter/examples/basic_framework/configs/http_simple.json +0 -23
- mcp_proxy_adapter/examples/basic_framework/configs/https_auth.json +0 -43
- mcp_proxy_adapter/examples/basic_framework/configs/https_no_protocol_middleware.json +0 -36
- mcp_proxy_adapter/examples/basic_framework/configs/https_simple.json +0 -29
- mcp_proxy_adapter/examples/basic_framework/configs/mtls_no_protocol_middleware.json +0 -34
- mcp_proxy_adapter/examples/basic_framework/configs/mtls_no_roles.json +0 -39
- mcp_proxy_adapter/examples/basic_framework/configs/mtls_simple.json +0 -35
- mcp_proxy_adapter/examples/basic_framework/configs/mtls_with_roles.json +0 -45
- mcp_proxy_adapter/examples/basic_framework/roles.json +0 -21
- mcp_proxy_adapter/examples/cert_config.json +0 -9
- mcp_proxy_adapter/examples/certs/admin.crt +0 -32
- mcp_proxy_adapter/examples/certs/admin.key +0 -52
- mcp_proxy_adapter/examples/certs/admin_cert.pem +0 -21
- mcp_proxy_adapter/examples/certs/admin_key.pem +0 -28
- mcp_proxy_adapter/examples/certs/ca_cert.pem +0 -23
- mcp_proxy_adapter/examples/certs/ca_cert.srl +0 -1
- mcp_proxy_adapter/examples/certs/ca_key.pem +0 -28
- mcp_proxy_adapter/examples/certs/cert_config.json +0 -9
- mcp_proxy_adapter/examples/certs/client.crt +0 -32
- mcp_proxy_adapter/examples/certs/client.key +0 -52
- mcp_proxy_adapter/examples/certs/client_admin.crt +0 -32
- mcp_proxy_adapter/examples/certs/client_admin.key +0 -52
- mcp_proxy_adapter/examples/certs/client_user.crt +0 -32
- mcp_proxy_adapter/examples/certs/client_user.key +0 -52
- mcp_proxy_adapter/examples/certs/guest_cert.pem +0 -21
- mcp_proxy_adapter/examples/certs/guest_key.pem +0 -28
- mcp_proxy_adapter/examples/certs/mcp_proxy_adapter_ca_ca.crt +0 -23
- mcp_proxy_adapter/examples/certs/proxy_cert.pem +0 -21
- mcp_proxy_adapter/examples/certs/proxy_key.pem +0 -28
- mcp_proxy_adapter/examples/certs/readonly.crt +0 -32
- mcp_proxy_adapter/examples/certs/readonly.key +0 -52
- mcp_proxy_adapter/examples/certs/readonly_cert.pem +0 -21
- mcp_proxy_adapter/examples/certs/readonly_key.pem +0 -28
- mcp_proxy_adapter/examples/certs/server.crt +0 -32
- mcp_proxy_adapter/examples/certs/server.key +0 -52
- mcp_proxy_adapter/examples/certs/server_cert.pem +0 -32
- mcp_proxy_adapter/examples/certs/server_key.pem +0 -52
- mcp_proxy_adapter/examples/certs/test_ca_ca.crt +0 -20
- mcp_proxy_adapter/examples/certs/user.crt +0 -32
- mcp_proxy_adapter/examples/certs/user.key +0 -52
- mcp_proxy_adapter/examples/certs/user_cert.pem +0 -21
- mcp_proxy_adapter/examples/certs/user_key.pem +0 -28
- mcp_proxy_adapter/examples/client_configs/api_key_client.json +0 -13
- mcp_proxy_adapter/examples/client_configs/basic_auth_client.json +0 -13
- mcp_proxy_adapter/examples/client_configs/certificate_client.json +0 -22
- mcp_proxy_adapter/examples/client_configs/jwt_client.json +0 -15
- mcp_proxy_adapter/examples/client_configs/no_auth_client.json +0 -9
- mcp_proxy_adapter/examples/full_application/configs/http_auth.json +0 -37
- mcp_proxy_adapter/examples/full_application/configs/http_simple.json +0 -23
- mcp_proxy_adapter/examples/full_application/configs/https_auth.json +0 -39
- mcp_proxy_adapter/examples/full_application/configs/https_simple.json +0 -25
- mcp_proxy_adapter/examples/full_application/configs/mtls_no_roles.json +0 -39
- mcp_proxy_adapter/examples/full_application/configs/mtls_with_roles.json +0 -45
- mcp_proxy_adapter/examples/full_application/roles.json +0 -21
- mcp_proxy_adapter/examples/keys/ca_key.pem +0 -28
- mcp_proxy_adapter/examples/keys/mcp_proxy_adapter_ca_ca.key +0 -28
- mcp_proxy_adapter/examples/keys/test_ca_ca.key +0 -28
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log +0 -220
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.1 +0 -1
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.2 +0 -1
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.3 +0 -1
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.4 +0 -1
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.5 +0 -1
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log +0 -220
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.1 +0 -1
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.2 +0 -1
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.3 +0 -1
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.4 +0 -1
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.5 +0 -1
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log +0 -2
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.1 +0 -1
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.2 +0 -1
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.3 +0 -1
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.4 +0 -1
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.5 +0 -1
- mcp_proxy_adapter/examples/roles.json +0 -38
- mcp_proxy_adapter/examples/server_configs/config_basic_http.json +0 -204
- mcp_proxy_adapter/examples/server_configs/config_http_token.json +0 -238
- mcp_proxy_adapter/examples/server_configs/config_https.json +0 -215
- mcp_proxy_adapter/examples/server_configs/config_https_token.json +0 -231
- mcp_proxy_adapter/examples/server_configs/config_mtls.json +0 -215
- mcp_proxy_adapter/examples/server_configs/config_proxy_registration.json +0 -250
- mcp_proxy_adapter/examples/server_configs/config_simple.json +0 -46
- mcp_proxy_adapter/examples/server_configs/roles.json +0 -38
- mcp_proxy_adapter-6.1.1.dist-info/METADATA +0 -205
- mcp_proxy_adapter-6.1.1.dist-info/RECORD +0 -197
- {mcp_proxy_adapter-6.1.1.dist-info → mcp_proxy_adapter-6.2.0.dist-info}/WHEEL +0 -0
- {mcp_proxy_adapter-6.1.1.dist-info → mcp_proxy_adapter-6.2.0.dist-info}/entry_points.txt +0 -0
- {mcp_proxy_adapter-6.1.1.dist-info → mcp_proxy_adapter-6.2.0.dist-info}/licenses/LICENSE +0 -0
- {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
|
-
|
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
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
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
|
-
|
601
|
-
|
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
|