mcp-proxy-adapter 6.7.2__py3-none-any.whl → 6.8.1__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.
@@ -156,25 +156,36 @@ def create_lifespan(config_path: Optional[str] = None):
156
156
  if not server_port:
157
157
  raise ValueError("server.port is required")
158
158
 
159
+ # Check port availability BEFORE starting registration manager
160
+ from mcp_proxy_adapter.core.utils import check_port_availability, handle_port_conflict
161
+
162
+ server_config = config.get("server", {})
163
+ server_host = server_config.get("host", "0.0.0.0")
164
+ server_port = server_config.get("port", 8000)
165
+
166
+ print(f"🔍 Checking external server port availability: {server_host}:{server_port}")
167
+ if not check_port_availability(server_host, server_port):
168
+ print(f"❌ CRITICAL: External server port {server_port} is occupied")
169
+ handle_port_conflict(server_host, server_port)
170
+ return # Exit the function immediately
171
+ print(f"✅ External server port {server_port} is available")
172
+
159
173
  # Determine registration URL using unified logic
160
174
  early_server_url = _determine_registration_url(config)
161
175
  try:
162
- from mcp_proxy_adapter.core.async_proxy_registration import (
163
- initialize_async_registration,
164
- start_async_registration,
176
+ from mcp_proxy_adapter.core.proxy_registration import (
177
+ register_with_proxy,
178
+ unregister_from_proxy,
179
+ initialize_proxy_registration,
165
180
  )
166
181
 
167
- # Initialize async registration manager
168
- async_manager = initialize_async_registration(config)
182
+ # Initialize proxy registration
183
+ initialize_proxy_registration()
169
184
  logger.info(
170
- "🔍 Initialized async registration manager with server_url: %s",
185
+ "🔍 Initialized proxy registration with server_url: %s",
171
186
  early_server_url,
172
187
  )
173
188
 
174
- # Start async registration in background thread
175
- start_async_registration(early_server_url)
176
- logger.info("🚀 Started async proxy registration and heartbeat thread")
177
-
178
189
  except Exception as e:
179
190
  logger.error(f"Failed to initialize async registration: {e}")
180
191
 
@@ -201,25 +212,12 @@ def create_lifespan(config_path: Optional[str] = None):
201
212
  # Determine registration URL using unified logic
202
213
  server_url = _determine_registration_url(final_config)
203
214
 
204
- # Update async registration manager with final server URL
215
+ # Update proxy registration with final server URL
205
216
  try:
206
- from mcp_proxy_adapter.core.async_proxy_registration import (
207
- get_async_registration_manager,
208
- )
209
-
210
- async_manager = get_async_registration_manager()
211
- if async_manager:
212
- async_manager.set_server_url(server_url)
213
- logger.info(f"🔍 Updated async registration manager with final server_url: {server_url}")
214
-
215
- # Get current status
216
- status = async_manager.get_status()
217
- logger.info(f"📊 Registration status: {status}")
218
- else:
219
- logger.warning("Async registration manager not available")
217
+ logger.info(f"🔍 Updated proxy registration with final server_url: {server_url}")
220
218
 
221
219
  except Exception as e:
222
- logger.error(f"Failed to update async registration manager: {e}")
220
+ logger.error(f"Failed to update proxy registration: {e}")
223
221
 
224
222
  try:
225
223
  print("🔍 Registration server_url resolved to (print):", server_url)
@@ -255,30 +253,16 @@ def create_lifespan(config_path: Optional[str] = None):
255
253
  # Shutdown events
256
254
  logger.info("Application shutting down")
257
255
 
258
- # Stop async registration manager (this will also unregister)
256
+ # Stop proxy registration (this will also unregister)
259
257
  try:
260
- from mcp_proxy_adapter.core.async_proxy_registration import (
261
- stop_async_registration,
262
- get_registration_status,
263
- )
264
-
265
- # Get final status before stopping
266
- final_status = get_registration_status()
267
- logger.info(f"📊 Final registration status: {final_status}")
268
-
269
- # Stop async registration (this will also unregister)
270
- stop_async_registration()
271
- logger.info("✅ Async registration manager stopped successfully")
272
-
273
- except Exception as e:
274
- logger.error(f"❌ Failed to stop async registration: {e}")
275
-
276
- # Fallback to old unregistration method
277
258
  unregistration_success = await unregister_from_proxy()
278
259
  if unregistration_success:
279
- logger.info("✅ Fallback proxy unregistration completed successfully")
260
+ logger.info("✅ Proxy unregistration completed successfully")
280
261
  else:
281
- logger.warning("⚠️ Fallback proxy unregistration failed or was disabled")
262
+ logger.warning("⚠️ Proxy unregistration failed or was disabled")
263
+
264
+ except Exception as e:
265
+ logger.error(f"❌ Failed to stop proxy registration: {e}")
282
266
 
283
267
  return lifespan
284
268
 
@@ -19,6 +19,11 @@ from mcp_proxy_adapter.config import config
19
19
  from mcp_proxy_adapter.commands.builtin_commands import (
20
20
  register_builtin_commands,
21
21
  )
22
+ from mcp_proxy_adapter.core.utils import (
23
+ check_port_availability,
24
+ handle_port_conflict,
25
+ find_port_for_internal_server,
26
+ )
22
27
 
23
28
  logger = get_logger("app_factory")
24
29
 
@@ -265,6 +270,7 @@ async def create_and_run_server(
265
270
  )
266
271
  print(f"🔌 Port: {server_port}")
267
272
 
273
+
268
274
  server_config = {
269
275
  "host": host,
270
276
  "port": server_port,
@@ -277,30 +283,6 @@ async def create_and_run_server(
277
283
  f"🔍 Debug: app_config keys: {list(app_config.keys()) if app_config else 'None'}"
278
284
  )
279
285
 
280
- # Check for SSL config in root section first (higher priority)
281
- if app_config and "ssl" in app_config:
282
- print(f"🔍 Debug: SSL config found in root: {app_config['ssl']}")
283
- print(
284
- f"🔍 Debug: SSL enabled: {app_config['ssl'].get('enabled', False)}"
285
- )
286
- if app_config["ssl"].get("enabled", False):
287
- ssl_config = app_config["ssl"]
288
- # Add SSL config directly to server_config for Hypercorn
289
- server_config["certfile"] = ssl_config.get("cert_file")
290
- server_config["keyfile"] = ssl_config.get("key_file")
291
- server_config["ca_certs"] = ssl_config.get(
292
- "ca_cert_file", ssl_config.get("ca_cert")
293
- )
294
- # Set verify_client based on verify_client setting
295
- verify_client = ssl_config.get("verify_client", False)
296
- server_config["verify_client"] = verify_client
297
- print(f"🔒 SSL enabled: {ssl_config.get('cert_file', 'N/A')}")
298
- print(
299
- f"🔒 SSL enabled: cert={ssl_config.get('cert_file')}, key={ssl_config.get('key_file')}"
300
- )
301
- print(
302
- f"🔒 Server config SSL: certfile={server_config.get('certfile')}, keyfile={server_config.get('keyfile')}, ca_certs={server_config.get('ca_certs')}, verify_mode={server_config.get('verify_mode')}"
303
- )
304
286
 
305
287
  # Check for SSL config in security section (fallback)
306
288
  if app_config and "security" in app_config:
@@ -331,28 +313,6 @@ async def create_and_run_server(
331
313
  print(
332
314
  f"🔒 Server config SSL: certfile={server_config.get('certfile')}, keyfile={server_config.get('keyfile')}, ca_certs={server_config.get('ca_certs')}, verify_mode={server_config.get('verify_mode')}"
333
315
  )
334
- print(f"🔍 Debug: SSL config found in root: {app_config['ssl']}")
335
- print(
336
- f"🔍 Debug: SSL enabled: {app_config['ssl'].get('enabled', False)}"
337
- )
338
- if app_config["ssl"].get("enabled", False):
339
- ssl_config = app_config["ssl"]
340
- # Add SSL config directly to server_config for Hypercorn
341
- server_config["certfile"] = ssl_config.get("cert_file")
342
- server_config["keyfile"] = ssl_config.get("key_file")
343
- server_config["ca_certs"] = ssl_config.get(
344
- "ca_cert_file", ssl_config.get("ca_cert")
345
- )
346
- # Set verify_client based on verify_client setting
347
- verify_client = ssl_config.get("verify_client", False)
348
- server_config["verify_client"] = verify_client
349
- print(f"🔒 SSL enabled: {ssl_config.get('cert_file', 'N/A')}")
350
- print(
351
- f"🔒 SSL enabled: cert={ssl_config.get('cert_file')}, key={ssl_config.get('key_file')}"
352
- )
353
- print(
354
- f"🔒 Server config SSL: certfile={server_config.get('certfile')}, keyfile={server_config.get('keyfile')}, ca_certs={server_config.get('ca_certs')}, verify_mode={server_config.get('verify_mode')}"
355
- )
356
316
 
357
317
  # 6. Start mTLS server if needed
358
318
  mtls_server = None
@@ -362,18 +322,21 @@ async def create_and_run_server(
362
322
  verify_client = ssl_config.get("verify_client", False)
363
323
 
364
324
  if verify_client:
365
- print("🔐 mTLS enabled - starting separate mTLS server...")
325
+ print("🔐 mTLS enabled - starting internal mTLS server...")
326
+ print(" External port: mTLS proxy (hypercorn)")
327
+ print(" Internal port: mTLS server (http.server)")
366
328
  from mcp_proxy_adapter.core.mtls_server import (
367
329
  start_mtls_server_thread,
368
330
  )
369
331
 
370
- # Start mTLS server in separate thread
332
+ # Start internal mTLS server in separate thread
333
+ # This server will find available port automatically if needed
371
334
  mtls_server = start_mtls_server_thread(app_config, main_app=app)
372
335
  if mtls_server:
373
- print(f"✅ mTLS server started on port {mtls_server.port}")
336
+ print(f"✅ Internal mTLS server started on port {mtls_server.port}")
374
337
  else:
375
338
  print(
376
- "⚠️ Failed to start mTLS server, continuing with regular HTTPS"
339
+ "⚠️ Failed to start internal mTLS server, continuing with regular HTTPS"
377
340
  )
378
341
  else:
379
342
  print("🔓 mTLS disabled - using regular HTTPS")
@@ -387,6 +350,8 @@ async def create_and_run_server(
387
350
  print(" Use Ctrl+C to stop the server")
388
351
  print("=" * 60)
389
352
 
353
+ # Port availability is already checked in api/app.py before registration manager starts
354
+
390
355
  # Use hypercorn directly
391
356
  import hypercorn.asyncio
392
357
  import hypercorn.config
@@ -428,13 +393,21 @@ async def create_and_run_server(
428
393
  if ssl_enabled:
429
394
  if verify_client:
430
395
  print(
431
- f"🔐 Starting HTTPS server with hypercorn (mTLS on separate port)..."
396
+ f"🔐 Starting external mTLS proxy with hypercorn (internal server on port {mtls_server.port if mtls_server else 'N/A'})..."
432
397
  )
433
398
  else:
434
399
  print(f"🔐 Starting HTTPS server with hypercorn...")
435
400
  else:
436
401
  print(f"🌐 Starting HTTP server with hypercorn...")
437
402
 
403
+ # Final port check before starting hypercorn
404
+ print(f"🔍 Final port check before starting hypercorn: {host}:{server_port}")
405
+ if not check_port_availability(host, server_port):
406
+ print(f"❌ CRITICAL: Port {server_port} is occupied during final check")
407
+ handle_port_conflict(host, server_port)
408
+ return # Exit immediately
409
+ print(f"✅ Final port check passed: {host}:{server_port}")
410
+
438
411
  # Run the server
439
412
  # hypercorn.asyncio.serve() should be run with asyncio.run(), not awaited
440
413
  # The function is designed to be the main entry point, not a coroutine to await
@@ -442,15 +415,28 @@ async def create_and_run_server(
442
415
 
443
416
  except KeyboardInterrupt:
444
417
  print("\n🛑 Server stopped by user")
445
- # Stop mTLS server if running
418
+ # Stop internal mTLS server if running
446
419
  if mtls_server:
447
- print("🛑 Stopping mTLS server...")
420
+ print("🛑 Stopping internal mTLS server...")
448
421
  mtls_server.stop()
422
+ except OSError as e:
423
+ if e.errno == 98: # Address already in use
424
+ print(f"\n❌ Port conflict detected: {e}")
425
+ handle_port_conflict(host, server_port)
426
+ else:
427
+ print(f"\n❌ Failed to start server: {e}")
428
+ # Stop mTLS server if running
429
+ if mtls_server:
430
+ print("🛑 Stopping mTLS server...")
431
+ mtls_server.stop()
432
+ import traceback
433
+ traceback.print_exc()
434
+ sys.exit(1)
449
435
  except Exception as e:
450
436
  print(f"\n❌ Failed to start server: {e}")
451
- # Stop mTLS server if running
437
+ # Stop internal mTLS server if running
452
438
  if mtls_server:
453
- print("🛑 Stopping mTLS server...")
439
+ print("🛑 Stopping internal mTLS server...")
454
440
  mtls_server.stop()
455
441
  import traceback
456
442
 
@@ -286,7 +286,11 @@ def start_mtls_server_thread(
286
286
  # Get server configuration
287
287
  server_config = config.get("server", {})
288
288
  host = server_config.get("host", "127.0.0.1")
289
- port = ssl_config.get("mtls_port", 8443) # Different port for mTLS
289
+ preferred_port = ssl_config.get("mtls_port", 8443) # Different port for mTLS
290
+
291
+ # For internal servers (mTLS), find available port if preferred is occupied
292
+ from mcp_proxy_adapter.core.utils import find_port_for_internal_server
293
+ port = find_port_for_internal_server(host, preferred_port)
290
294
 
291
295
  # Get certificate paths
292
296
  cert_file = ssl_config.get("cert_file", "certs/localhost_server.crt")
@@ -79,10 +79,12 @@ class SignalHandler:
79
79
  except Exception as e:
80
80
  logger.error(f"❌ Shutdown callback failed: {e}")
81
81
 
82
- # If this is SIGINT (Ctrl+C), we might want to exit immediately after a delay
83
- if signum == signal.SIGINT:
84
- logger.info("⏰ SIGINT received, will exit in 5 seconds if not stopped gracefully...")
85
- threading.Timer(5.0, self._force_exit).start()
82
+ # Force exit immediately to avoid server hang
83
+ logger.info("🔄 Force exiting to avoid server hang...")
84
+ import os
85
+ # Use os._exit for immediate termination
86
+ logger.warning("⚠️ Using os._exit for immediate termination...")
87
+ os._exit(0)
86
88
 
87
89
  def _force_exit(self):
88
90
  """Force exit if graceful shutdown takes too long."""
@@ -5,6 +5,7 @@ Module with utility functions for the microservice.
5
5
  import hashlib
6
6
  import json
7
7
  import os
8
+ import socket
8
9
  import sys
9
10
  import time
10
11
  import uuid
@@ -138,3 +139,144 @@ def ensure_directory(path: str) -> bool:
138
139
  except Exception as e:
139
140
  logger.error(f"Error creating directory {path}: {e}")
140
141
  return False
142
+
143
+
144
+ def check_port_availability(host: str, port: int, timeout: float = 1.0) -> bool:
145
+ """
146
+ Checks if a port is available for binding.
147
+
148
+ Args:
149
+ host: Host address to check
150
+ port: Port number to check
151
+ timeout: Connection timeout in seconds
152
+
153
+ Returns:
154
+ True if port is available, False if port is in use
155
+ """
156
+ try:
157
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
158
+ sock.settimeout(timeout)
159
+ result = sock.connect_ex((host, port))
160
+ return result != 0 # True if connection failed (port is free)
161
+ except Exception as e:
162
+ logger.warning(f"Error checking port {port} on {host}: {e}")
163
+ return True # Assume port is available if check fails
164
+
165
+
166
+ def find_available_port(host: str, start_port: int, max_attempts: int = 100) -> Optional[int]:
167
+ """
168
+ Finds an available port starting from the specified port.
169
+
170
+ Args:
171
+ host: Host address to check
172
+ start_port: Starting port number
173
+ max_attempts: Maximum number of ports to check
174
+
175
+ Returns:
176
+ Available port number or None if no port found
177
+ """
178
+ for port in range(start_port, start_port + max_attempts):
179
+ if check_port_availability(host, port):
180
+ return port
181
+ return None
182
+
183
+
184
+ def get_port_usage_info(port: int) -> str:
185
+ """
186
+ Gets information about what process is using a port.
187
+
188
+ Args:
189
+ port: Port number to check
190
+
191
+ Returns:
192
+ String with port usage information
193
+ """
194
+ try:
195
+ import subprocess
196
+ result = subprocess.run(
197
+ ["lsof", "-i", f":{port}"],
198
+ capture_output=True,
199
+ text=True,
200
+ timeout=5
201
+ )
202
+ if result.returncode == 0 and result.stdout.strip():
203
+ return f"Port {port} is used by:\n{result.stdout.strip()}"
204
+ else:
205
+ return f"Port {port} appears to be in use but process info unavailable"
206
+ except Exception as e:
207
+ return f"Port {port} is in use (unable to get process info: {e})"
208
+
209
+
210
+ def handle_port_conflict(host: str, port: int) -> None:
211
+ """
212
+ Handles port conflict with user-friendly error message and suggestions.
213
+ This is for MAIN server port conflicts - application must exit.
214
+
215
+ Args:
216
+ host: Host address
217
+ port: Port number that's in conflict
218
+ """
219
+ print(f"❌ ERROR: Port {port} is already in use on {host}")
220
+ print(f"💡 Suggestions:")
221
+ print(f" 1. Choose a different port: --port <different_port>")
222
+ print(f" 2. Stop the conflicting service")
223
+ print(f" 3. Check what's using the port:")
224
+ print(f" lsof -i :{port}")
225
+ print(f" netstat -tulpn | grep :{port}")
226
+
227
+ # Try to get more detailed info about port usage
228
+ usage_info = get_port_usage_info(port)
229
+ print(f"🔍 Port usage details:")
230
+ print(f" {usage_info}")
231
+
232
+ # Try to find an alternative port
233
+ alt_port = find_available_port(host, port + 1, 10)
234
+ if alt_port:
235
+ print(f"💡 Alternative port suggestion: {alt_port}")
236
+ print(f" Try: --port {alt_port}")
237
+ else:
238
+ print(f"💡 Try ports in range {port + 1}-{port + 20}")
239
+
240
+ print(f"🛑 Application cannot start due to port conflict")
241
+ sys.exit(1)
242
+
243
+
244
+ def find_port_for_internal_server(host: str, preferred_port: int) -> int:
245
+ """
246
+ Finds an available port for internal server (mTLS, etc.).
247
+ If preferred port is occupied, finds any available port.
248
+
249
+ Args:
250
+ host: Host address
251
+ preferred_port: Preferred port number
252
+
253
+ Returns:
254
+ Available port number
255
+ """
256
+ # First try the preferred port
257
+ if check_port_availability(host, preferred_port):
258
+ print(f"✅ Internal server port {preferred_port} is available")
259
+ return preferred_port
260
+
261
+ # If preferred port is occupied, find any available port
262
+ print(f"⚠️ Internal server preferred port {preferred_port} is occupied, searching for alternative...")
263
+
264
+ alt_port = find_available_port(host, preferred_port + 1, 50)
265
+ if alt_port:
266
+ print(f"✅ Found alternative port for internal server: {alt_port}")
267
+ return alt_port
268
+
269
+ # If no port found in range, try from 9000
270
+ alt_port = find_available_port(host, 9000, 100)
271
+ if alt_port:
272
+ print(f"✅ Found alternative port for internal server: {alt_port}")
273
+ return alt_port
274
+
275
+ # Last resort - try from 10000
276
+ alt_port = find_available_port(host, 10000, 100)
277
+ if alt_port:
278
+ print(f"✅ Found alternative port for internal server: {alt_port}")
279
+ return alt_port
280
+
281
+ # If still no port found, raise error
282
+ raise RuntimeError(f"Unable to find available port for internal server on {host}")
@@ -25,27 +25,26 @@ def main():
25
25
  parser.add_argument("--port", type=int, help="Server port")
26
26
  parser.add_argument("--debug", action="store_true", help="Enable debug mode")
27
27
  args = parser.parse_args()
28
- # Override configuration if specified
29
- config_overrides = {}
28
+ print(f"🚀 Starting Basic Framework Example")
29
+ print(f"📋 Configuration: {args.config}")
30
30
  if args.host:
31
- config_overrides["host"] = args.host
31
+ print(f"🌐 Host override: {args.host}")
32
32
  if args.port:
33
- config_overrides["port"] = args.port
33
+ print(f"🔌 Port override: {args.port}")
34
34
  if args.debug:
35
- config_overrides["debug"] = True
36
- print(f"🚀 Starting Basic Framework Example")
37
- print(f"📋 Configuration: {args.config}")
35
+ print(f"🔧 Debug mode: enabled")
38
36
  print("=" * 50)
39
- # Use the factory method to create and run the server
37
+
38
+ # Note: Host and port overrides should be handled in configuration file
39
+ # or by modifying the configuration before passing to create_and_run_server
40
40
  import asyncio
41
41
  asyncio.run(create_and_run_server(
42
42
  config_path=args.config,
43
43
  title="Basic Framework Example",
44
44
  description="Basic MCP Proxy Adapter with minimal configuration",
45
45
  version="1.0.0",
46
- host=config_overrides.get("host"),
47
- port=config_overrides.get("port"),
48
- debug=config_overrides.get("debug", False),
46
+ host=args.host or "0.0.0.0",
47
+ log_level="debug" if args.debug else "info"
49
48
  ))
50
49
 
51
50
 
@@ -93,74 +93,24 @@ class FullApplication:
93
93
  self.logger.info("✅ Application created successfully")
94
94
 
95
95
  def run(self, host: str = None, port: int = None, debug: bool = False):
96
- """Run the application using the factory method."""
97
- # Override configuration if specified
98
- config_overrides = {}
99
- if host:
100
- config_overrides["host"] = host
101
- if port:
102
- config_overrides["port"] = port
103
- if debug:
104
- config_overrides["debug"] = True
96
+ """Run the application using the factory method with port checking."""
105
97
  print(f"🚀 Starting Full Application Example")
106
98
  print(f"📋 Configuration: {self.config_path}")
107
99
  print(
108
100
  f"🔧 Features: Built-in commands, Custom commands, Dynamic commands, Hooks, Proxy endpoints"
109
101
  )
110
102
  print("=" * 60)
111
- # Create application with configuration
112
- self.create_application()
113
- # Get server configuration
114
- server_host = self.config.get("server.host", "0.0.0.0")
115
- server_port = self.config.get("server.port", 8000)
116
- server_debug = self.config.get("server.debug", False)
117
- # Get SSL configuration
118
- ssl_enabled = self.config.get("ssl.enabled", False)
119
- ssl_cert_file = self.config.get("ssl.cert_file")
120
- ssl_key_file = self.config.get("ssl.key_file")
121
- ssl_ca_cert = self.config.get("ssl.ca_cert")
122
- verify_client = self.config.get("ssl.verify_client", False)
123
- print(f"🌐 Server: {server_host}:{server_port}")
124
- print(f"🔧 Debug: {server_debug}")
125
- if ssl_enabled:
126
- print(f"🔐 SSL: Enabled")
127
- print(f" Certificate: {ssl_cert_file}")
128
- print(f" Key: {ssl_key_file}")
129
- if ssl_ca_cert:
130
- print(f" CA: {ssl_ca_cert}")
131
- print(f" Client verification: {verify_client}")
132
- print("=" * 60)
133
- # Use hypercorn directly to run the application with proxy endpoints
134
- try:
135
- import hypercorn.asyncio
136
- import hypercorn.config
137
- import asyncio
138
-
139
- # Configure hypercorn
140
- config_hypercorn = hypercorn.config.Config()
141
- config_hypercorn.bind = [f"{server_host}:{server_port}"]
142
- config_hypercorn.loglevel = "debug" if server_debug else "info"
143
- if ssl_enabled and ssl_cert_file and ssl_key_file:
144
- config_hypercorn.certfile = ssl_cert_file
145
- config_hypercorn.keyfile = ssl_key_file
146
- if ssl_ca_cert:
147
- config_hypercorn.ca_certs = ssl_ca_cert
148
- if verify_client:
149
- import ssl
150
-
151
- config_hypercorn.verify_mode = ssl.CERT_REQUIRED
152
- print(f"🔐 Starting HTTPS server with hypercorn...")
153
- else:
154
- print(f"🌐 Starting HTTP server with hypercorn...")
155
- # Run the server
156
- asyncio.run(hypercorn.asyncio.serve(self.app, config_hypercorn))
157
- except ImportError:
158
- print("❌ hypercorn not installed. Installing...")
159
- import subprocess
160
-
161
- subprocess.run([sys.executable, "-m", "pip", "install", "hypercorn"])
162
- print("✅ hypercorn installed. Please restart the application.")
163
- return
103
+
104
+ # Use the factory method to create and run the server with port checking
105
+ import asyncio
106
+ asyncio.run(create_and_run_server(
107
+ config_path=self.config_path,
108
+ title="Full Application Example",
109
+ description="Complete MCP Proxy Adapter with all features",
110
+ version="1.0.0",
111
+ host=host or "0.0.0.0",
112
+ log_level="debug" if debug else "info"
113
+ ))
164
114
 
165
115
 
166
116
  def main():
mcp_proxy_adapter/main.py CHANGED
@@ -22,6 +22,11 @@ from mcp_proxy_adapter.api.app import create_app
22
22
  from mcp_proxy_adapter.config import Config
23
23
  from mcp_proxy_adapter.core.config_validator import ConfigValidator
24
24
  from mcp_proxy_adapter.core.signal_handler import setup_signal_handling, is_shutdown_requested
25
+ from mcp_proxy_adapter.core.utils import (
26
+ check_port_availability,
27
+ handle_port_conflict,
28
+ find_port_for_internal_server,
29
+ )
25
30
 
26
31
 
27
32
  def main():
@@ -84,6 +89,13 @@ def main():
84
89
  host = config.get("server.host", "0.0.0.0")
85
90
  port = config.get("server.port", 8000)
86
91
 
92
+ # Check external port availability - this is critical, must exit if occupied
93
+ print(f"🔍 Checking external server port availability: {host}:{port}")
94
+ if not check_port_availability(host, port):
95
+ print(f"❌ CRITICAL: External server port {port} is occupied")
96
+ handle_port_conflict(host, port)
97
+ print(f"✅ External server port {port} is available")
98
+
87
99
  # Get protocol and SSL configuration
88
100
  protocol = config.get("server.protocol", "http")
89
101
  verify_client = config.get("transport.verify_client", False)
@@ -98,6 +110,16 @@ def main():
98
110
  hypercorn_port = port + 1000 # internal port
99
111
  mtls_proxy_port = port # external port
100
112
  ssl_enabled = True
113
+
114
+ # Check internal port availability (flexible - find alternative if occupied)
115
+ print(f"🔍 Checking internal server port availability: {hypercorn_host}:{hypercorn_port}")
116
+ if not check_port_availability(hypercorn_host, hypercorn_port):
117
+ print(f"⚠️ Internal server preferred port {hypercorn_port} is occupied, searching for alternative...")
118
+ hypercorn_port = find_port_for_internal_server(hypercorn_host, hypercorn_port)
119
+ print(f"✅ Internal server will use port: {hypercorn_port}")
120
+ else:
121
+ print(f"✅ Internal server port {hypercorn_port} is available")
122
+
101
123
  print(f"🔐 mTLS Mode: hypercorn on {hypercorn_host}:{hypercorn_port}, mTLS proxy on {host}:{mtls_proxy_port}")
102
124
  else:
103
125
  # Regular mode: hypercorn on external port (no proxy needed)
@@ -2,4 +2,4 @@
2
2
  Version information for MCP Proxy Adapter.
3
3
  """
4
4
 
5
- __version__ = "6.7.2"
5
+ __version__ = "6.8.1"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mcp-proxy-adapter
3
- Version: 6.7.2
3
+ Version: 6.8.1
4
4
  Summary: Powerful JSON-RPC microservices framework with built-in security, authentication, and proxy registration
5
5
  Home-page: https://github.com/maverikod/mcp-proxy-adapter
6
6
  Author: Vasiliy Zdanovskiy