mcp-proxy-adapter 6.6.1__py3-none-any.whl → 6.6.4__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 (27) hide show
  1. mcp_proxy_adapter/api/app.py +61 -55
  2. mcp_proxy_adapter/commands/command_registry.py +27 -13
  3. mcp_proxy_adapter/config.py +1 -8
  4. mcp_proxy_adapter/core/proxy_registration.py +80 -7
  5. mcp_proxy_adapter/core/server_adapter.py +1 -1
  6. mcp_proxy_adapter/examples/check_config.py +1 -1
  7. mcp_proxy_adapter/examples/config_builder.py +13 -19
  8. mcp_proxy_adapter/examples/{generate_certificates_bugfix.py → generate_certificates.py} +11 -0
  9. mcp_proxy_adapter/examples/generate_config.py +3 -3
  10. mcp_proxy_adapter/examples/run_full_test_suite.py +3 -3
  11. mcp_proxy_adapter/examples/security_test_client.py +6 -5
  12. mcp_proxy_adapter/examples/test_chk_hostname_automated.py +7 -10
  13. mcp_proxy_adapter/examples/test_framework_complete.py +269 -0
  14. mcp_proxy_adapter/examples/test_mcp_server.py +188 -0
  15. mcp_proxy_adapter/main.py +19 -18
  16. mcp_proxy_adapter/version.py +1 -1
  17. {mcp_proxy_adapter-6.6.1.dist-info → mcp_proxy_adapter-6.6.4.dist-info}/METADATA +1 -1
  18. {mcp_proxy_adapter-6.6.1.dist-info → mcp_proxy_adapter-6.6.4.dist-info}/RECORD +21 -25
  19. mcp_proxy_adapter/examples/config_builder_simple.py +0 -271
  20. mcp_proxy_adapter/examples/generate_all_certificates.py +0 -487
  21. mcp_proxy_adapter/examples/generate_certificates_cli.py +0 -406
  22. mcp_proxy_adapter/examples/generate_certificates_fixed.py +0 -313
  23. mcp_proxy_adapter/examples/generate_certificates_framework.py +0 -366
  24. mcp_proxy_adapter/examples/generate_certificates_openssl.py +0 -391
  25. {mcp_proxy_adapter-6.6.1.dist-info → mcp_proxy_adapter-6.6.4.dist-info}/WHEEL +0 -0
  26. {mcp_proxy_adapter-6.6.1.dist-info → mcp_proxy_adapter-6.6.4.dist-info}/entry_points.txt +0 -0
  27. {mcp_proxy_adapter-6.6.1.dist-info → mcp_proxy_adapter-6.6.4.dist-info}/top_level.txt +0 -0
@@ -65,11 +65,8 @@ def create_lifespan(config_path: Optional[str] = None):
65
65
  initialize_proxy_registration,
66
66
  )
67
67
 
68
- # Initialize proxy registration manager early
69
- try:
70
- initialize_proxy_registration(config.get_all())
71
- except Exception as e:
72
- logger.error(f"Failed to initialize proxy registration: {e}")
68
+ # Proxy registration manager will be initialized in registry.reload_system()
69
+ # after all commands are loaded to ensure complete command schema
73
70
 
74
71
  # Compute server_url EARLY and inject into registration manager so
75
72
  # that reload_system (which may perform registration) uses the correct
@@ -88,11 +85,11 @@ def create_lifespan(config_path: Optional[str] = None):
88
85
  public_host = reg_cfg.get("public_host")
89
86
  public_port = reg_cfg.get("public_port")
90
87
 
91
- security_config = config.get("security", {})
92
- ssl_config = security_config.get("ssl", {})
93
- if not ssl_config.get("enabled", False):
94
- ssl_config = config.get("ssl", {})
95
- protocol = "https" if ssl_config.get("enabled", False) else "http"
88
+ # Check SSL configuration from new structure
89
+ protocol = config.get("server.protocol", "http")
90
+ verify_client = config.get("transport.verify_client", False)
91
+ ssl_enabled = protocol in ["https", "mtls"] or verify_client
92
+ protocol = "https" if ssl_enabled else "http"
96
93
 
97
94
  import os
98
95
  docker_host_addr = os.getenv("DOCKER_HOST_ADDR", "172.17.0.1")
@@ -124,11 +121,7 @@ def create_lifespan(config_path: Optional[str] = None):
124
121
  )
125
122
  logger.info(f"System initialization result: {init_result}")
126
123
 
127
- # Initialize proxy registration manager with current config
128
- try:
129
- initialize_proxy_registration(config.get_all())
130
- except Exception as e:
131
- logger.error(f"Failed to initialize proxy registration: {e}")
124
+ # Proxy registration manager is already initialized in registry.reload_system()
132
125
 
133
126
  # Recompute registration URL AFTER config reload using final config
134
127
  try:
@@ -141,11 +134,11 @@ def create_lifespan(config_path: Optional[str] = None):
141
134
  public_host = reg_cfg.get("public_host")
142
135
  public_port = reg_cfg.get("public_port")
143
136
 
144
- security_config = final_config.get("security", {})
145
- ssl_cfg = security_config.get("ssl", {})
146
- if not ssl_cfg.get("enabled", False):
147
- ssl_cfg = final_config.get("ssl", {})
148
- protocol = "https" if ssl_cfg.get("enabled", False) else "http"
137
+ # Determine protocol using the new configuration structure
138
+ protocol_cfg = final_config.get("server", {}).get("protocol", "http")
139
+ verify_client_cfg = final_config.get("transport", {}).get("verify_client", False)
140
+ ssl_enabled_final = protocol_cfg in ["https", "mtls"] or verify_client_cfg
141
+ protocol = "https" if ssl_enabled_final else "http"
149
142
 
150
143
  import os
151
144
  docker_host_addr = os.getenv("DOCKER_HOST_ADDR", "172.17.0.1")
@@ -184,19 +177,26 @@ def create_lifespan(config_path: Optional[str] = None):
184
177
  logger.error(f"Failed to recompute registration URL: {e}")
185
178
  server_url = early_server_url
186
179
 
187
- # Attempt proxy registration in background with small delay
188
- async def _delayed_register():
180
+ # Proxy registration is now handled in registry.reload_system()
181
+ # after all commands are loaded, ensuring complete command schema
182
+ logger.info("ℹ️ Proxy registration will be handled after command loading completes")
183
+
184
+ # Add delayed registration task to allow server to fully start
185
+ async def delayed_registration():
186
+ """Delayed registration to ensure server is fully started."""
187
+ await asyncio.sleep(2) # Wait for server to start listening
188
+ logger.info("🔄 Attempting delayed proxy registration after server startup")
189
189
  try:
190
- await asyncio.sleep(0.5)
190
+ from mcp_proxy_adapter.core.proxy_registration import register_with_proxy
191
191
  success = await register_with_proxy(server_url)
192
192
  if success:
193
- logger.info("✅ Proxy registration completed successfully")
193
+ logger.info("✅ Delayed proxy registration successful")
194
194
  else:
195
- logger.info("ℹ️ Proxy registration is disabled or failed")
195
+ logger.warning("⚠️ Delayed proxy registration failed")
196
196
  except Exception as e:
197
- logger.error(f"Proxy registration failed: {e}")
198
-
199
- asyncio.create_task(_delayed_register())
197
+ logger.error(f" Delayed proxy registration error: {e}")
198
+
199
+ asyncio.create_task(delayed_registration())
200
200
 
201
201
  yield # Application is running
202
202
 
@@ -227,20 +227,30 @@ def create_ssl_context(
227
227
  """
228
228
  current_config = app_config if app_config is not None else config.get_all()
229
229
 
230
- # Try security framework SSL config first
231
- security_config = current_config.get("security", {})
232
- ssl_config = security_config.get("ssl", {})
233
-
234
- # Fallback to legacy SSL config
235
- if not ssl_config.get("enabled", False):
236
- ssl_config = current_config.get("ssl", {})
230
+ # Check SSL configuration from new structure
231
+ protocol = current_config.get("server", {}).get("protocol", "http")
232
+ verify_client = current_config.get("transport", {}).get("verify_client", False)
233
+ ssl_enabled = protocol in ["https", "mtls"] or verify_client
237
234
 
238
- if not ssl_config.get("enabled", False):
235
+ if not ssl_enabled:
239
236
  logger.info("SSL is disabled in configuration")
240
237
  return None
241
238
 
242
- cert_file = ssl_config.get("cert_file")
243
- key_file = ssl_config.get("key_file")
239
+ # Get certificate paths from configuration
240
+ cert_file = current_config.get("transport", {}).get("cert_file")
241
+ key_file = current_config.get("transport", {}).get("key_file")
242
+ ca_cert = current_config.get("transport", {}).get("ca_cert")
243
+
244
+ # Convert relative paths to absolute paths
245
+ if cert_file and not Path(cert_file).is_absolute():
246
+ project_root = Path(__file__).parent.parent.parent
247
+ cert_file = str(project_root / cert_file)
248
+ if key_file and not Path(key_file).is_absolute():
249
+ project_root = Path(__file__).parent.parent.parent
250
+ key_file = str(project_root / key_file)
251
+ if ca_cert and not Path(ca_cert).is_absolute():
252
+ project_root = Path(__file__).parent.parent.parent
253
+ ca_cert = str(project_root / ca_cert)
244
254
 
245
255
  if not cert_file or not key_file:
246
256
  logger.warning("SSL enabled but certificate or key file not specified")
@@ -251,15 +261,15 @@ def create_ssl_context(
251
261
  ssl_context = SSLUtils.create_ssl_context(
252
262
  cert_file=cert_file,
253
263
  key_file=key_file,
254
- ca_cert=ssl_config.get("ca_cert"),
255
- verify_client=ssl_config.get("verify_client", False),
256
- cipher_suites=ssl_config.get("cipher_suites", []),
257
- min_tls_version=ssl_config.get("min_tls_version", "1.2"),
258
- max_tls_version=ssl_config.get("max_tls_version", "1.3"),
264
+ ca_cert=ca_cert,
265
+ verify_client=current_config.get("transport", {}).get("verify_client", False),
266
+ cipher_suites=[],
267
+ min_tls_version="1.2",
268
+ max_tls_version="1.3",
259
269
  )
260
270
 
261
271
  logger.info(
262
- f"SSL context created successfully for mode: {ssl_config.get('mode', 'https_only')}"
272
+ f"SSL context created successfully for mode: https_only"
263
273
  )
264
274
  return ssl_context
265
275
 
@@ -308,17 +318,13 @@ def create_app(
308
318
  print(
309
319
  f"🔍 Debug: create_app received app_config keys: {list(app_config.keys())}"
310
320
  )
311
- if "security" in app_config:
312
- ssl_config = app_config["security"].get("ssl", {})
313
- print(
314
- f"🔍 Debug: create_app SSL config: enabled={ssl_config.get('enabled', False)}"
315
- )
316
- print(
317
- f"🔍 Debug: create_app SSL config: cert_file={ssl_config.get('cert_file')}"
318
- )
319
- print(
320
- f"🔍 Debug: create_app SSL config: key_file={ssl_config.get('key_file')}"
321
- )
321
+ # Debug SSL configuration
322
+ protocol = app_config.get("server", {}).get("protocol", "http")
323
+ verify_client = app_config.get("transport", {}).get("verify_client", False)
324
+ ssl_enabled = protocol in ["https", "mtls"] or verify_client
325
+ print(f"🔍 Debug: create_app SSL config: enabled={ssl_enabled}")
326
+ print(f"🔍 Debug: create_app protocol: {protocol}")
327
+ print(f"🔍 Debug: create_app verify_client: {verify_client}")
322
328
  else:
323
329
  print(f"🔍 Debug: create_app received app_config type: {type(app_config)}")
324
330
  else:
@@ -786,23 +786,37 @@ class CommandRegistry:
786
786
  # Initialize proxy registration manager with current config
787
787
  initialize_proxy_registration(config.get_all())
788
788
 
789
- # Get server configuration
789
+ # Get server configuration with proper URL resolution logic
790
790
  server_config = config.get("server", {})
791
791
  server_host = server_config.get("host", "0.0.0.0")
792
792
  server_port = server_config.get("port", 8000)
793
793
 
794
- # Determine server URL based on SSL configuration
795
- ssl_config = config.get("ssl", {})
796
- if ssl_config.get("enabled", False):
797
- protocol = "https"
798
- else:
799
- protocol = "http"
800
-
801
- # Use localhost for external access if host is 0.0.0.0
802
- if server_host == "0.0.0.0":
803
- server_host = "localhost"
804
-
805
- server_url = f"{protocol}://{server_host}:{server_port}"
794
+ # Get registration configuration for public host/port overrides
795
+ # First check server config, then registration config
796
+ public_host = config.get("server.public_host")
797
+ public_port = config.get("server.public_port")
798
+
799
+ # Fallback to registration config if not found in server
800
+ if not public_host or not public_port:
801
+ reg_cfg = config.get("registration", config.get("proxy_registration", {}))
802
+ public_host = public_host or reg_cfg.get("public_host")
803
+ public_port = public_port or reg_cfg.get("public_port")
804
+
805
+ # Determine protocol based on new configuration structure
806
+ protocol = config.get("server.protocol", "http")
807
+ verify_client = config.get("transport.verify_client", False)
808
+ ssl_enabled = protocol in ["https", "mtls"] or verify_client
809
+ protocol = "https" if ssl_enabled else "http"
810
+
811
+ # Resolve host and port (same logic as in app.py)
812
+ import os
813
+ docker_host_addr = os.getenv("DOCKER_HOST_ADDR", "172.17.0.1")
814
+ resolved_host = public_host or (docker_host_addr if server_host == "0.0.0.0" else server_host)
815
+ resolved_port = public_port or server_port
816
+
817
+ server_url = f"{protocol}://{resolved_host}:{resolved_port}"
818
+
819
+ logger.info(f"🔍 Proxy registration URL resolved: {server_url}")
806
820
 
807
821
  # Attempt proxy registration
808
822
  proxy_registration_success = await register_with_proxy(server_url)
@@ -84,15 +84,8 @@ class Config:
84
84
  "transport": {
85
85
  "type": "http",
86
86
  "port": None,
87
- "ssl": {
88
- "enabled": False,
89
- "cert_file": None,
90
- "key_file": None,
91
- "ca_cert": None,
92
87
  "verify_client": False,
93
- "client_cert_required": False,
94
- "chk_hostname": False, # Default to False when SSL is disabled
95
- },
88
+ "chk_hostname": False, # Default to False for HTTP
96
89
  },
97
90
  "proxy_registration": {
98
91
  "enabled": False,
@@ -13,6 +13,7 @@ email: vasilyvz@gmail.com
13
13
  import asyncio
14
14
  import time
15
15
  import ssl
16
+ import traceback
16
17
  from typing import Dict, Any, Optional, Tuple
17
18
  from pathlib import Path
18
19
  from urllib.parse import urljoin
@@ -248,10 +249,15 @@ class ProxyRegistrationManager:
248
249
  """
249
250
  logger.debug("_create_ssl_context called")
250
251
 
251
- # Check if we're in HTTP mode - if so, don't create SSL context
252
- server_config = self.config.get("server", {})
253
- if server_config.get("protocol", "http").lower() == "http":
254
- logger.debug("HTTP mode detected, skipping SSL context creation")
252
+ # Decide SSL strictly by proxy URL scheme: use SSL only for https proxy URLs
253
+ try:
254
+ from urllib.parse import urlparse as _urlparse
255
+ scheme = _urlparse(self.proxy_url).scheme if self.proxy_url else "http"
256
+ if scheme.lower() != "https":
257
+ logger.debug("Proxy URL is HTTP, skipping SSL context creation for registration")
258
+ return None
259
+ except Exception:
260
+ logger.debug("Failed to parse proxy_url, assuming HTTP and skipping SSL context")
255
261
  return None
256
262
 
257
263
  if not self.client_security:
@@ -440,7 +446,11 @@ class ProxyRegistrationManager:
440
446
 
441
447
  if success:
442
448
  self.registered = True
443
- self.server_key = result.get("server_key")
449
+ # Safely extract server_key from result
450
+ if isinstance(result, dict):
451
+ self.server_key = result.get("server_key")
452
+ else:
453
+ self.server_key = None
444
454
  logger.info(
445
455
  f"✅ Successfully registered with proxy. Server key: {self.server_key}"
446
456
  )
@@ -455,8 +465,65 @@ class ProxyRegistrationManager:
455
465
  else:
456
466
  # Be robust if result is not a dict
457
467
  error_msg = None
468
+ logger.error(f"DEBUG: result type = {type(result)}, result = {result}")
458
469
  if isinstance(result, dict):
459
- error_msg = result.get("error", {}).get("message", "Unknown error")
470
+ logger.error(f"DEBUG: result is dict, getting error field")
471
+ error_field = result.get("error", {})
472
+ logger.error(f"DEBUG: error_field type = {type(error_field)}, error_field = {error_field}")
473
+ if isinstance(error_field, dict):
474
+ error_msg = error_field.get("message", "Unknown error")
475
+ elif isinstance(error_field, str):
476
+ error_msg = error_field
477
+ else:
478
+ error_msg = str(error_field)
479
+
480
+ # Auto-recovery: already registered case → force unregistration then retry once
481
+ error_code = result.get("error_code") or (result.get("error", {}).get("code") if isinstance(result.get("error"), dict) else None)
482
+ already_registered = False
483
+ existing_server_key = None
484
+ # Prefer structured detail if provided
485
+ if isinstance(result.get("details"), dict):
486
+ existing_server_key = result.get("details", {}).get("existing_server_key")
487
+ # Fallback: parse from error message text
488
+ if not existing_server_key and isinstance(error_msg, str) and "already registered as" in error_msg:
489
+ try:
490
+ # Expecting: "... already registered as <server_id>_<copy_number>"
491
+ tail = error_msg.split("already registered as", 1)[1].strip()
492
+ existing_server_key = tail.split()[0]
493
+ except Exception:
494
+ existing_server_key = None
495
+
496
+ if (error_code in ("DUPLICATE_SERVER_URL", "REGISTRATION_ERROR") or already_registered) and existing_server_key:
497
+ try:
498
+ logger.info(f"Attempting auto-unregistration of existing instance: {existing_server_key}")
499
+ # Build unregistration payload using parsed server_key
500
+ try:
501
+ copy_number = int(existing_server_key.split("_")[-1])
502
+ except Exception:
503
+ copy_number = 1
504
+ unregistration_data = {"server_id": self.server_id, "copy_number": copy_number}
505
+ # Reuse secure unregistration request directly
506
+ unreg_success, _unreg_result = await self._make_secure_unregistration_request(unregistration_data)
507
+ if unreg_success:
508
+ logger.info("Auto-unregistration succeeded, retrying registration once...")
509
+ # Retry registration once immediately
510
+ retry_success, retry_result = await self._make_secure_registration_request(registration_data)
511
+ if retry_success:
512
+ self.registered = True
513
+ if isinstance(retry_result, dict):
514
+ self.server_key = retry_result.get("server_key")
515
+ else:
516
+ self.server_key = None
517
+ logger.info(f"✅ Successfully registered after auto-unregistration. Server key: {self.server_key}")
518
+ if self.registration_config.get("heartbeat", {}).get("enabled", True):
519
+ await self._start_heartbeat()
520
+ return True
521
+ else:
522
+ logger.warning(f"Retry registration failed after auto-unregistration: {retry_result}")
523
+ else:
524
+ logger.warning(f"Auto-unregistration failed: {_unreg_result}")
525
+ except Exception as _auto_e:
526
+ logger.warning(f"Auto-unregistration/registration flow error: {_auto_e}")
460
527
  else:
461
528
  error_msg = str(result)
462
529
  logger.warning(
@@ -467,6 +534,7 @@ class ProxyRegistrationManager:
467
534
  logger.error(
468
535
  f"❌ Registration attempt {attempt + 1} failed with exception: {e}"
469
536
  )
537
+ logger.error(f"Full traceback: {traceback.format_exc()}")
470
538
 
471
539
  logger.error(
472
540
  f"❌ Failed to register with proxy after {self.retry_attempts} attempts"
@@ -562,8 +630,10 @@ class ProxyRegistrationManager:
562
630
  ) as response:
563
631
  try:
564
632
  result = await response.json()
565
- except Exception:
633
+ logger.debug(f"Response JSON parsed successfully: {type(result)} - {result}")
634
+ except Exception as e:
566
635
  text_body = await response.text()
636
+ logger.debug(f"JSON parsing failed: {e}, text_body: {text_body}")
567
637
  result = {"success": False, "error": {"code": "NON_JSON_RESPONSE", "message": text_body}}
568
638
 
569
639
  # Validate response headers if security framework available
@@ -604,6 +674,9 @@ class ProxyRegistrationManager:
604
674
  logger.warning(
605
675
  f"Registration failed with HTTP status: {response.status}"
606
676
  )
677
+ # Ensure result is a dict for consistent error handling
678
+ if isinstance(result, str):
679
+ result = {"success": False, "error": {"code": "HTTP_ERROR", "message": result}}
607
680
  return False, result
608
681
  finally:
609
682
  if connector:
@@ -65,7 +65,7 @@ class ServerConfigAdapter:
65
65
 
66
66
  # Map verification mode
67
67
  if ssl_config.get("verify_client", False):
68
- hypercorn_ssl["verify_mode"] = "CERT_REQUIRED"
68
+ hypercorn_ssl["verify_mode"] = 2 # ssl.CERT_REQUIRED
69
69
 
70
70
  # Map hostname checking
71
71
  if "chk_hostname" in ssl_config:
@@ -15,7 +15,7 @@ from pathlib import Path
15
15
  from typing import Dict, Any, List, Optional
16
16
 
17
17
  # Add the project root to the path
18
- sys.path.insert(0, str(Path(__file__).parent))
18
+ sys.path.insert(0, str(Path(__file__).parent.parent.parent))
19
19
 
20
20
  from mcp_proxy_adapter.config import Config
21
21
  from mcp_proxy_adapter.examples.config_builder import ConfigBuilder, Protocol, AuthMethod
@@ -76,16 +76,9 @@ class ConfigBuilder:
76
76
  },
77
77
  "transport": {
78
78
  "type": "http",
79
- "port": None,
80
- "ssl": {
81
- "enabled": False,
82
- "cert_file": None,
83
- "key_file": None,
84
- "ca_cert": None,
85
- "verify_client": False,
86
- "client_cert_required": False,
87
- "chk_hostname": False,
88
- },
79
+ "port": None,
80
+ "verify_client": False,
81
+ "chk_hostname": False
89
82
  }
90
83
  }
91
84
 
@@ -94,18 +87,19 @@ class ConfigBuilder:
94
87
  self.config["server"]["protocol"] = protocol.value
95
88
 
96
89
  if protocol == Protocol.HTTP:
97
- # HTTP - no SSL
98
- pass
90
+ # HTTP - no SSL, no client verification
91
+ self.config["transport"]["verify_client"] = False
92
+ self.config["transport"]["chk_hostname"] = False
99
93
 
100
94
  elif protocol == Protocol.HTTPS:
101
- # HTTPS - server SSL only
102
- # SSL configuration will be handled by the server based on protocol
103
- pass
95
+ # HTTPS - SSL enabled, no client verification
96
+ self.config["transport"]["verify_client"] = False
97
+ self.config["transport"]["chk_hostname"] = True
104
98
 
105
99
  elif protocol == Protocol.MTLS:
106
- # mTLS - server SSL + client certificates
107
- # SSL configuration will be handled by the server based on protocol
108
- pass
100
+ # mTLS - SSL enabled, client verification required
101
+ self.config["transport"]["verify_client"] = True
102
+ self.config["transport"]["chk_hostname"] = True
109
103
 
110
104
  return self
111
105
 
@@ -237,7 +231,7 @@ class ConfigFactory:
237
231
  .set_auth(AuthMethod.TOKEN)
238
232
  .set_server(port=port)
239
233
  .build())
240
-
234
+
241
235
  @staticmethod
242
236
  def create_mtls_token_roles_config(port: int = 8008) -> Dict[str, Any]:
243
237
  """Create mTLS with token and roles configuration."""
@@ -229,7 +229,18 @@ class BugfixCertificateGenerator:
229
229
  with open(client_info["output_key"], 'w') as f:
230
230
  f.write(result.private_key_pem)
231
231
 
232
+ # Also create a copy in certs/ directory for easier access
233
+ cert_name_base = client_info["common_name"].replace("-", "_")
234
+ certs_cert = self.certs_dir / f"{cert_name_base}_client.crt"
235
+ certs_key = self.certs_dir / f"{cert_name_base}_client.key"
236
+
237
+ with open(certs_cert, 'w') as f:
238
+ f.write(result.certificate_pem)
239
+ with open(certs_key, 'w') as f:
240
+ f.write(result.private_key_pem)
241
+
232
242
  self.print_success(f"{cert_name} certificate generated: {client_info['output_cert']}")
243
+ self.print_success(f"Also created: {certs_cert} and {certs_key}")
233
244
  return True
234
245
  else:
235
246
  self.print_error(f"Failed to generate {cert_name} certificate")
@@ -12,10 +12,10 @@ import sys
12
12
  from pathlib import Path
13
13
  from typing import Dict, Any, Optional
14
14
 
15
- # Add the examples directory to the path to import config_builder
16
- sys.path.insert(0, str(Path(__file__).parent / "mcp_proxy_adapter" / "examples"))
15
+ # Add the project root to the path to import config_builder
16
+ sys.path.insert(0, str(Path(__file__).parent.parent.parent))
17
17
 
18
- from config_builder import ConfigBuilder, ConfigFactory, Protocol, AuthMethod
18
+ from mcp_proxy_adapter.examples.config_builder import ConfigBuilder, ConfigFactory, Protocol, AuthMethod
19
19
 
20
20
 
21
21
  def create_config_from_flags(
@@ -113,7 +113,7 @@ class FullTestSuiteRunner:
113
113
 
114
114
  try:
115
115
  # Check if certificate generation script exists
116
- cert_script = self.working_dir / "generate_certificates_bugfix.py"
116
+ cert_script = self.working_dir / "mcp_proxy_adapter" / "examples" / "generate_certificates.py"
117
117
  if not cert_script.exists():
118
118
  self.print_error(
119
119
  f"Certificate generation script not found: {cert_script}"
@@ -167,7 +167,7 @@ class FullTestSuiteRunner:
167
167
 
168
168
  try:
169
169
  # Check if create_test_configs.py exists
170
- config_script = self.working_dir / "create_test_configs.py"
170
+ config_script = self.working_dir / "mcp_proxy_adapter" / "examples" / "create_test_configs.py"
171
171
  if not config_script.exists():
172
172
  self.print_error(f"Configuration generator not found: {config_script}")
173
173
  return False
@@ -194,7 +194,7 @@ class FullTestSuiteRunner:
194
194
  # Run the configuration generator
195
195
  cmd = [
196
196
  sys.executable,
197
- "create_test_configs.py",
197
+ "mcp_proxy_adapter/examples/create_test_configs.py",
198
198
  "--comprehensive-config",
199
199
  "comprehensive_config.json",
200
200
  ]
@@ -124,11 +124,12 @@ class SecurityTestClient:
124
124
  def create_ssl_context_for_mtls(self) -> ssl.SSLContext:
125
125
  """Create SSL context for mTLS connections."""
126
126
  # For mTLS, we need client certificates - check if they exist
127
- # Paths are relative to project root (../../ from examples directory)
128
- # Use admin certificates to match server configuration
129
- cert_file = "../../certs/admin.crt"
130
- key_file = "../../certs/client_admin.key"
131
- ca_cert_file = "../../certs/localhost_server.crt"
127
+ # Use absolute paths to avoid issues with working directory
128
+ # Use newly generated admin_client_client.crt which is signed by the same CA as server
129
+ project_root = Path(__file__).parent.parent.parent
130
+ cert_file = str(project_root / "certs" / "admin_client_client.crt")
131
+ key_file = str(project_root / "keys" / "admin-client_client.key")
132
+ ca_cert_file = str(project_root / "certs" / "localhost_server.crt")
132
133
 
133
134
  # CRITICAL: For mTLS, certificates are REQUIRED
134
135
  if not os.path.exists(cert_file):
@@ -13,7 +13,7 @@ from pathlib import Path
13
13
  import sys
14
14
 
15
15
  # Add the project root to the path
16
- sys.path.insert(0, str(Path(__file__).parent))
16
+ sys.path.insert(0, str(Path(__file__).parent.parent.parent))
17
17
 
18
18
  from mcp_proxy_adapter.config import Config
19
19
  from mcp_proxy_adapter.examples.config_builder import ConfigBuilder, Protocol, AuthMethod
@@ -27,7 +27,7 @@ def test_chk_hostname_default_config():
27
27
 
28
28
  # Default should be HTTP with chk_hostname=False
29
29
  assert config.get("server.protocol") == "http"
30
- assert config.get("transport.ssl.chk_hostname") is False
30
+ assert config.get("transport.chk_hostname") is False
31
31
 
32
32
  print("✅ Default config: chk_hostname=False for HTTP")
33
33
 
@@ -49,7 +49,7 @@ def test_chk_hostname_http_config():
49
49
 
50
50
  # HTTP should have chk_hostname=False
51
51
  assert config.get("server.protocol") == "http"
52
- assert config.get("transport.ssl.chk_hostname") is False
52
+ assert config.get("transport.chk_hostname") is False
53
53
 
54
54
  print("✅ HTTP config: chk_hostname=False")
55
55
  finally:
@@ -74,7 +74,7 @@ def test_chk_hostname_https_config():
74
74
 
75
75
  # HTTPS should have chk_hostname=True
76
76
  assert config.get("server.protocol") == "https"
77
- assert config.get("transport.ssl.chk_hostname") is True
77
+ assert config.get("transport.chk_hostname") is True
78
78
 
79
79
  print("✅ HTTPS config: chk_hostname=True")
80
80
  finally:
@@ -98,7 +98,7 @@ def test_chk_hostname_mtls_config():
98
98
 
99
99
  # mTLS should have chk_hostname=True
100
100
  assert config.get("server.protocol") == "mtls"
101
- assert config.get("transport.ssl.chk_hostname") is True
101
+ assert config.get("transport.chk_hostname") is True
102
102
 
103
103
  print("✅ mTLS config: chk_hostname=True")
104
104
  finally:
@@ -114,10 +114,7 @@ def test_chk_hostname_override():
114
114
  # Add transport section if it doesn't exist
115
115
  if "transport" not in https_config:
116
116
  https_config["transport"] = {}
117
- if "ssl" not in https_config["transport"]:
118
- https_config["transport"]["ssl"] = {}
119
- https_config["transport"]["ssl"]["chk_hostname"] = False
120
- https_config["transport"]["ssl"]["_chk_hostname_user_set"] = True
117
+ https_config["transport"]["chk_hostname"] = False
121
118
 
122
119
  # Save to temporary file and load with Config
123
120
  with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
@@ -130,7 +127,7 @@ def test_chk_hostname_override():
130
127
 
131
128
  # Should respect the override
132
129
  assert config.get("server.protocol") == "https"
133
- assert config.get("transport.ssl.chk_hostname") is False
130
+ assert config.get("transport.chk_hostname") is False
134
131
 
135
132
  print("✅ HTTPS config with chk_hostname=False override works")
136
133
  finally: