mcp-proxy-adapter 6.2.21__py3-none-any.whl → 6.2.23__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.
@@ -21,6 +21,7 @@ from typing import Dict, List, Optional, Any
21
21
  from dataclasses import dataclass
22
22
  import aiohttp
23
23
  from aiohttp import ClientSession, ClientTimeout, TCPConnector
24
+
24
25
  # Add project root to path for imports
25
26
  project_root = Path(__file__).parent.parent.parent
26
27
  current_dir = Path(__file__).parent
@@ -28,6 +29,26 @@ parent_dir = current_dir.parent
28
29
  sys.path.insert(0, str(project_root))
29
30
  sys.path.insert(0, str(current_dir))
30
31
  sys.path.insert(0, str(parent_dir))
32
+
33
+ # Import mcp_security_framework components
34
+ try:
35
+ from mcp_security_framework import SSLManager, CertificateManager
36
+ from mcp_security_framework.schemas.config import SSLConfig
37
+ _MCP_SECURITY_AVAILABLE = True
38
+ print("✅ mcp_security_framework available")
39
+ except ImportError:
40
+ _MCP_SECURITY_AVAILABLE = False
41
+ print("⚠️ mcp_security_framework not available, falling back to standard SSL")
42
+
43
+ # Import cryptography components
44
+ try:
45
+ from cryptography import x509
46
+ from cryptography.hazmat.primitives import serialization
47
+ _CRYPTOGRAPHY_AVAILABLE = True
48
+ print("✅ cryptography available")
49
+ except ImportError:
50
+ _CRYPTOGRAPHY_AVAILABLE = False
51
+ print("⚠️ cryptography not available, SSL validation will be limited")
31
52
  @dataclass
32
53
  class TestResult:
33
54
  """Test result data class."""
@@ -45,11 +66,33 @@ class SecurityTestClient:
45
66
  """Initialize security test client."""
46
67
  self.base_url = base_url
47
68
  self.session: Optional[ClientSession] = None
48
- # Note: For basic testing, we'll use simple SSL context creation
49
- # instead of full mcp_security_framework integration
50
- self.security_manager = None
69
+
70
+ # Initialize security managers if available
51
71
  self.ssl_manager = None
52
- self.auth_manager = None
72
+ self.cert_manager = None
73
+ self._security_available = _MCP_SECURITY_AVAILABLE
74
+ self._crypto_available = _CRYPTOGRAPHY_AVAILABLE
75
+
76
+ if self._security_available:
77
+ try:
78
+ # Initialize SSL manager with default config
79
+ ssl_config = SSLConfig(
80
+ enabled=True,
81
+ cert_file=None,
82
+ key_file=None,
83
+ ca_cert_file=None,
84
+ verify_mode="CERT_NONE", # For testing
85
+ min_tls_version="TLSv1.2"
86
+ )
87
+ self.ssl_manager = SSLManager(ssl_config)
88
+ print("✅ SSL Manager initialized with mcp_security_framework")
89
+ except Exception as e:
90
+ print(f"⚠️ Failed to initialize SSL Manager: {e}")
91
+ self._security_available = False
92
+
93
+ if not self._security_available:
94
+ print("ℹ️ Using standard SSL library for testing")
95
+ self.ssl_manager = None
53
96
  self.test_results: List[TestResult] = []
54
97
  # Test tokens
55
98
  self.test_tokens = {
@@ -85,14 +128,33 @@ class SecurityTestClient:
85
128
  return self
86
129
  def create_ssl_context_for_mtls(self) -> ssl.SSLContext:
87
130
  """Create SSL context for mTLS connections."""
88
- ssl_context = ssl.create_default_context()
89
- # For mTLS testing
131
+ if self.ssl_manager and self._security_available:
132
+ try:
133
+ # Use mcp_security_framework for mTLS
134
+ cert_file = "./certs/user_cert.pem"
135
+ key_file = "./certs/user_key.pem"
136
+ ca_cert_file = "./certs/mcp_proxy_adapter_ca_ca.crt"
137
+
138
+ return self.ssl_manager.create_client_context(
139
+ ca_cert_file=ca_cert_file if os.path.exists(ca_cert_file) else None,
140
+ client_cert_file=cert_file if os.path.exists(cert_file) else None,
141
+ client_key_file=key_file if os.path.exists(key_file) else None,
142
+ verify_mode="CERT_NONE", # For testing
143
+ min_version="TLSv1.2"
144
+ )
145
+ except Exception as e:
146
+ print(f"⚠️ Failed to create mTLS context with mcp_security_framework: {e}")
147
+ print("ℹ️ Falling back to standard SSL")
148
+
149
+ # Fallback to standard SSL
150
+ ssl_context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
151
+ # For mTLS testing - client needs to present certificate to server
90
152
  ssl_context.check_hostname = False
91
- ssl_context.verify_mode = ssl.CERT_NONE
92
- # Load client certificate and key
153
+ ssl_context.verify_mode = ssl.CERT_NONE # Don't verify server cert for testing
154
+ # Load client certificate and key for mTLS
93
155
  cert_file = "./certs/user_cert.pem"
94
156
  key_file = "./certs/user_key.pem"
95
- ca_cert_file = "./certs/ca_cert.pem"
157
+ ca_cert_file = "./certs/mcp_proxy_adapter_ca_ca.crt"
96
158
  if os.path.exists(cert_file) and os.path.exists(key_file):
97
159
  ssl_context.load_cert_chain(certfile=cert_file, keyfile=key_file)
98
160
  if os.path.exists(ca_cert_file):
@@ -106,6 +168,21 @@ class SecurityTestClient:
106
168
  key_file: Optional[str] = None,
107
169
  ca_cert_file: Optional[str] = None) -> ssl.SSLContext:
108
170
  """Create SSL context for client."""
171
+ if self.ssl_manager and self._security_available:
172
+ try:
173
+ # Use mcp_security_framework for SSL context creation
174
+ return self.ssl_manager.create_client_context(
175
+ ca_cert_file=ca_cert_file if ca_cert_file and os.path.exists(ca_cert_file) else None,
176
+ client_cert_file=cert_file if cert_file and os.path.exists(cert_file) else None,
177
+ client_key_file=key_file if key_file and os.path.exists(key_file) else None,
178
+ verify_mode="CERT_NONE", # For testing
179
+ min_version="TLSv1.2"
180
+ )
181
+ except Exception as e:
182
+ print(f"⚠️ Failed to create SSL context with mcp_security_framework: {e}")
183
+ print("ℹ️ Falling back to standard SSL")
184
+
185
+ # Fallback to standard SSL
109
186
  ssl_context = ssl.create_default_context()
110
187
  # For testing with self-signed certificates
111
188
  ssl_context.check_hostname = False
@@ -121,8 +198,10 @@ class SecurityTestClient:
121
198
  """Create authentication headers."""
122
199
  headers = {"Content-Type": "application/json"}
123
200
  if auth_type == "api_key":
124
- token = kwargs.get("token", "test-token-123") # Use correct token
125
- headers["X-API-Key"] = token # Use X-API-Key header
201
+ token = kwargs.get("token", "test-token-123")
202
+ # Provide both common header styles to maximize compatibility
203
+ headers["X-API-Key"] = token
204
+ headers["Authorization"] = f"Bearer {token}"
126
205
  elif auth_type == "basic":
127
206
  username = kwargs.get("username", "admin")
128
207
  password = kwargs.get("password", "password")
@@ -306,39 +385,104 @@ class SecurityTestClient:
306
385
  start_time = time.time()
307
386
  test_name = f"Negative Auth ({auth_type})"
308
387
  try:
309
- # Use invalid token
310
- headers = self.create_auth_headers("api_key", token="invalid-token-999")
311
- data = {
312
- "jsonrpc": "2.0",
313
- "method": "echo",
314
- "params": {"message": "Should fail"},
315
- "id": 3
316
- }
317
- async with self.session.post(f"{server_url}/cmd",
318
- headers=headers,
319
- json=data) as response:
320
- duration = time.time() - start_time
321
- # Expected to fail with 401
322
- if response.status == 401:
323
- return TestResult(
324
- test_name=test_name,
325
- server_url=server_url,
326
- auth_type=auth_type,
327
- success=True, # This is expected to fail
328
- status_code=response.status,
329
- response_data={"expected": "authentication_failure"},
330
- duration=duration
331
- )
332
- else:
388
+ if auth_type == "certificate":
389
+ # For mTLS, test with invalid/expired certificate or no certificate
390
+ import aiohttp
391
+ from aiohttp import ClientTimeout, TCPConnector
392
+ import ssl
393
+
394
+ # Create SSL context with wrong certificate (should be rejected)
395
+ ssl_context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
396
+ ssl_context.check_hostname = False
397
+ # Don't load any client certificate - this should cause rejection
398
+ # Load CA certificate for server verification
399
+ ca_cert_file = "./certs/mcp_proxy_adapter_ca_ca.crt"
400
+ if os.path.exists(ca_cert_file):
401
+ ssl_context.load_verify_locations(cafile=ca_cert_file)
402
+ ssl_context.verify_mode = ssl.CERT_NONE # Don't verify server cert for testing
403
+
404
+ connector = TCPConnector(ssl=ssl_context)
405
+ timeout = ClientTimeout(total=10) # Shorter timeout
406
+
407
+ try:
408
+ async with aiohttp.ClientSession(timeout=timeout, connector=connector) as temp_session:
409
+ data = {
410
+ "jsonrpc": "2.0",
411
+ "method": "echo",
412
+ "params": {"message": "Should fail without certificate"},
413
+ "id": 3
414
+ }
415
+ async with temp_session.post(f"{server_url}/cmd", json=data) as response:
416
+ duration = time.time() - start_time
417
+ # If we get here, the server accepted the connection without proper certificate
418
+ # This is actually a security issue - server should reject
419
+ return TestResult(
420
+ test_name=test_name,
421
+ server_url=server_url,
422
+ auth_type=auth_type,
423
+ success=False,
424
+ status_code=response.status,
425
+ error_message=f"SECURITY ISSUE: mTLS server accepted connection without client certificate (status: {response.status})",
426
+ duration=duration
427
+ )
428
+ except (aiohttp.ClientError, aiohttp.ServerDisconnectedError, asyncio.TimeoutError) as e:
429
+ # This is expected - server should reject connections without proper certificate
430
+ duration = time.time() - start_time
333
431
  return TestResult(
334
432
  test_name=test_name,
335
433
  server_url=server_url,
336
434
  auth_type=auth_type,
337
- success=False,
338
- status_code=response.status,
339
- error_message=f"Expected 401, got {response.status}",
435
+ success=True,
436
+ status_code=0,
437
+ response_data={"expected": "connection_rejected", "error": str(e)},
340
438
  duration=duration
341
439
  )
440
+ else:
441
+ # For other auth types, use invalid token
442
+ headers = self.create_auth_headers("api_key", token="invalid-token-999")
443
+ data = {
444
+ "jsonrpc": "2.0",
445
+ "method": "echo",
446
+ "params": {"message": "Should fail"},
447
+ "id": 3
448
+ }
449
+ async with self.session.post(f"{server_url}/cmd",
450
+ headers=headers,
451
+ json=data) as response:
452
+ duration = time.time() - start_time
453
+ # Expect 401 only when auth is enforced
454
+ expects_auth = auth_type in ("api_key", "certificate", "basic")
455
+ if expects_auth and response.status == 401:
456
+ return TestResult(
457
+ test_name=test_name,
458
+ server_url=server_url,
459
+ auth_type=auth_type,
460
+ success=True,
461
+ status_code=response.status,
462
+ response_data={"expected": "authentication_failure"},
463
+ duration=duration
464
+ )
465
+ elif not expects_auth and response.status == 200:
466
+ # Security disabled: negative auth should not fail
467
+ return TestResult(
468
+ test_name=test_name,
469
+ server_url=server_url,
470
+ auth_type=auth_type,
471
+ success=True,
472
+ status_code=response.status,
473
+ response_data={"expected": "no_auth_required"},
474
+ duration=duration
475
+ )
476
+ else:
477
+ return TestResult(
478
+ test_name=test_name,
479
+ server_url=server_url,
480
+ auth_type=auth_type,
481
+ success=False,
482
+ status_code=response.status,
483
+ error_message=f"Unexpected status for negative auth: {response.status}",
484
+ duration=duration
485
+ )
342
486
  except Exception as e:
343
487
  duration = time.time() - start_time
344
488
  return TestResult(
@@ -99,6 +99,18 @@ def setup_test_environment(output_dir: Path) -> None:
99
99
  if cert_tokens_src.exists():
100
100
  shutil.copy2(cert_tokens_src, output_dir / "scripts/")
101
101
  print("✅ Copied generate_certificates_and_tokens.py")
102
+
103
+ # Copy roles.json to the root directory for compatibility
104
+ roles_src = examples_src_root / "roles.json"
105
+ if roles_src.exists():
106
+ shutil.copy2(roles_src, output_dir)
107
+ print("✅ Copied roles.json to root directory")
108
+
109
+ # Also copy from configs directory if it exists
110
+ roles_configs_src = output_dir / "configs" / "roles.json"
111
+ if roles_configs_src.exists():
112
+ shutil.copy2(roles_configs_src, output_dir / "roles.json")
113
+ print("✅ Updated roles.json from configs directory")
102
114
  print(
103
115
  "🎉 Test environment setup completed successfully at: {}".format(
104
116
  output_dir
mcp_proxy_adapter/main.py CHANGED
@@ -7,6 +7,7 @@ email: vasilyvz@gmail.com
7
7
  """
8
8
 
9
9
  import sys
10
+ import ssl
10
11
  import hypercorn.asyncio
11
12
  import hypercorn.config
12
13
  import asyncio
@@ -32,7 +33,10 @@ def main():
32
33
  config = Config(config_path=args.config)
33
34
  else:
34
35
  config = Config()
35
-
36
+
37
+ print(f"DEBUG main.py: config type: {type(config)}")
38
+ print(f"DEBUG main.py: config.get_all() type: {type(config.get_all())}")
39
+
36
40
  # Create application
37
41
  app = create_app(app_config=config)
38
42
 
@@ -70,8 +74,14 @@ def main():
70
74
  config_hypercorn.ca_certs = ssl_ca_cert
71
75
 
72
76
  if verify_client:
73
- import ssl
77
+ # For mTLS, require client certificates
78
+ config_hypercorn.set_cert_reqs(ssl.CERT_REQUIRED)
74
79
  config_hypercorn.verify_mode = ssl.CERT_REQUIRED
80
+ print("🔐 mTLS: Client certificate verification enabled")
81
+ else:
82
+ # For regular HTTPS without client verification
83
+ config_hypercorn.set_cert_reqs(ssl.CERT_NONE)
84
+ config_hypercorn.verify_mode = ssl.CERT_NONE
75
85
 
76
86
  print(f"🔐 Starting HTTPS server with hypercorn...")
77
87
  else: