mcp-proxy-adapter 6.7.2__py3-none-any.whl → 6.8.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/api/app.py +14 -0
- mcp_proxy_adapter/core/app_factory.py +41 -55
- mcp_proxy_adapter/core/mtls_server.py +5 -1
- mcp_proxy_adapter/core/signal_handler.py +6 -4
- mcp_proxy_adapter/core/utils.py +142 -0
- mcp_proxy_adapter/examples/basic_framework/main.py +10 -11
- mcp_proxy_adapter/examples/full_application/main.py +12 -62
- mcp_proxy_adapter/main.py +22 -0
- mcp_proxy_adapter/version.py +1 -1
- {mcp_proxy_adapter-6.7.2.dist-info → mcp_proxy_adapter-6.8.0.dist-info}/METADATA +1 -1
- {mcp_proxy_adapter-6.7.2.dist-info → mcp_proxy_adapter-6.8.0.dist-info}/RECORD +14 -16
- mcp_proxy_adapter/core/async_proxy_registration.py +0 -285
- mcp_proxy_adapter/examples/generate_certificates.py +0 -385
- {mcp_proxy_adapter-6.7.2.dist-info → mcp_proxy_adapter-6.8.0.dist-info}/WHEEL +0 -0
- {mcp_proxy_adapter-6.7.2.dist-info → mcp_proxy_adapter-6.8.0.dist-info}/entry_points.txt +0 -0
- {mcp_proxy_adapter-6.7.2.dist-info → mcp_proxy_adapter-6.8.0.dist-info}/top_level.txt +0 -0
mcp_proxy_adapter/api/app.py
CHANGED
@@ -156,6 +156,20 @@ 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:
|
@@ -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
|
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
|
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
|
-
|
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
|
-
#
|
83
|
-
|
84
|
-
|
85
|
-
|
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."""
|
mcp_proxy_adapter/core/utils.py
CHANGED
@@ -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
|
-
|
29
|
-
|
28
|
+
print(f"🚀 Starting Basic Framework Example")
|
29
|
+
print(f"📋 Configuration: {args.config}")
|
30
30
|
if args.host:
|
31
|
-
|
31
|
+
print(f"🌐 Host override: {args.host}")
|
32
32
|
if args.port:
|
33
|
-
|
33
|
+
print(f"🔌 Port override: {args.port}")
|
34
34
|
if args.debug:
|
35
|
-
|
36
|
-
print(f"🚀 Starting Basic Framework Example")
|
37
|
-
print(f"📋 Configuration: {args.config}")
|
35
|
+
print(f"🔧 Debug mode: enabled")
|
38
36
|
print("=" * 50)
|
39
|
-
|
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=
|
47
|
-
|
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
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
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)
|
mcp_proxy_adapter/version.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: mcp-proxy-adapter
|
3
|
-
Version: 6.
|
3
|
+
Version: 6.8.0
|
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
|
@@ -2,11 +2,11 @@ mcp_proxy_adapter/__init__.py,sha256=iH0EBBsRj_cfZJpAIsgN_8tTdfefhnl6uUKHjLHhWDQ
|
|
2
2
|
mcp_proxy_adapter/__main__.py,sha256=sq3tANRuTd18euamt0Bmn1sJeAyzXENZ5VvsMwbrDFA,579
|
3
3
|
mcp_proxy_adapter/config.py,sha256=QpoPaUKcWJ-eu6HYphhIZmkc2M-p1JgpLFAgolf_l5s,20161
|
4
4
|
mcp_proxy_adapter/custom_openapi.py,sha256=XRviX-C-ZkSKdBhORhDTdeN_1FWyEfXZADiASft3t9I,28149
|
5
|
-
mcp_proxy_adapter/main.py,sha256=
|
5
|
+
mcp_proxy_adapter/main.py,sha256=NFcSW7WaEnimRWe5zj28D0CH9otHlRZ92d2Um6XiGjE,10399
|
6
6
|
mcp_proxy_adapter/openapi.py,sha256=2UZOI09ZDRJuBYBjKbMyb2U4uASszoCMD5o_4ktRpvg,13480
|
7
|
-
mcp_proxy_adapter/version.py,sha256=
|
7
|
+
mcp_proxy_adapter/version.py,sha256=w-T-AD4G77RDBgrZmgrms3YSfVxVCwAp18S8uPaF7gA,74
|
8
8
|
mcp_proxy_adapter/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
9
|
-
mcp_proxy_adapter/api/app.py,sha256
|
9
|
+
mcp_proxy_adapter/api/app.py,sha256=-kEeQraHghvYgJ2A3y0pAg-otpRlo0dWONlLV-pNrEA,38646
|
10
10
|
mcp_proxy_adapter/api/handlers.py,sha256=X-hcMNVeTAu4yVkKJEChEsj2bFptUS6sLNN-Wysjkow,10011
|
11
11
|
mcp_proxy_adapter/api/schemas.py,sha256=mevUvQnYgWQfkJAs3-vq3HalBzh6-Saa-Au1VVf0peE,12377
|
12
12
|
mcp_proxy_adapter/api/tool_integration.py,sha256=AeUyvJVN-c3FrX5fHdagHL51saRH5d1ZKqc2YEx0rTE,10147
|
@@ -54,9 +54,8 @@ mcp_proxy_adapter/commands/token_management_command.py,sha256=tCVjhWqAQ3KhcwSsZU
|
|
54
54
|
mcp_proxy_adapter/commands/transport_management_command.py,sha256=HEnUyL4S014jheyBwO90u9gnzk0qxBlOJdC_0Sxhq9E,4585
|
55
55
|
mcp_proxy_adapter/commands/unload_command.py,sha256=6CUM9B9c-mNxw7uvt2vcvZSnxMySfoMT5UmDhzNXq38,4984
|
56
56
|
mcp_proxy_adapter/core/__init__.py,sha256=3yt0CFZdsIG8Ln4bg-r4ISYzipm3ZUAxTn0twYTs9FI,867
|
57
|
-
mcp_proxy_adapter/core/app_factory.py,sha256=
|
57
|
+
mcp_proxy_adapter/core/app_factory.py,sha256=OuxUjGpOp5mOPIQ5iumBW42bxAD0H6o2HM8UICgpUvA,20492
|
58
58
|
mcp_proxy_adapter/core/app_runner.py,sha256=n8_rBojzKUpHLN5ksiXcR8UWoBYk6Tt5OtWk-X2kVPc,11868
|
59
|
-
mcp_proxy_adapter/core/async_proxy_registration.py,sha256=hM_ZV2YoKM8n0MakVMBd2KnYTJBh1igDeb4DYutfHnE,11387
|
60
59
|
mcp_proxy_adapter/core/auth_validator.py,sha256=q8TNkdolvP-gM6Bvecc6nrVG9al5J31pocdwhguhTBk,19742
|
61
60
|
mcp_proxy_adapter/core/certificate_utils.py,sha256=yeDwi-j42CxK_g-r5_ragGFY_HdSgDfTWHVUjDHF6nI,38480
|
62
61
|
mcp_proxy_adapter/core/client.py,sha256=qIbPl8prEwK2U65kl-vGJW2_imo1E4i6HxG_VpPeWpQ,21168
|
@@ -70,7 +69,7 @@ mcp_proxy_adapter/core/logging.py,sha256=gNI6vfPQC7jrUtVu6NeDsmU72JPlrRRBhtJipL1
|
|
70
69
|
mcp_proxy_adapter/core/mtls_asgi.py,sha256=tvk0P9024s18dcCHY9AaQIecT4ojOTv21EuQWXwooU0,5200
|
71
70
|
mcp_proxy_adapter/core/mtls_asgi_app.py,sha256=DT_fTUH1RkvBa3ThbyCyNb-XUHyCb4DqaKA1gcZC6z4,6538
|
72
71
|
mcp_proxy_adapter/core/mtls_proxy.py,sha256=5APlWs0ImiHGEC65W_7F-PbVO3NZ2BVSj9r14AcUtTE,6011
|
73
|
-
mcp_proxy_adapter/core/mtls_server.py,sha256=
|
72
|
+
mcp_proxy_adapter/core/mtls_server.py,sha256=GBNhLezDYSXFP1xcNaThEhBqZlxXUGp0q0OT2Ibb2M0,10366
|
74
73
|
mcp_proxy_adapter/core/protocol_manager.py,sha256=iaXWsfm1XSfemz5QQBesMluc4cwf-LtuZVi9bm1uj28,14680
|
75
74
|
mcp_proxy_adapter/core/proxy_client.py,sha256=CB6KBhV3vH2GU5nZ27VZ_xlNbYTAU_tnYFrkuK5He58,6094
|
76
75
|
mcp_proxy_adapter/core/proxy_registration.py,sha256=QlQFHnjIN8ETWcasPDUXNMfMsU_-KdrvLTYJiDua_wI,41237
|
@@ -81,11 +80,11 @@ mcp_proxy_adapter/core/security_integration.py,sha256=-5I4i9so_yMjc-zuGO-7zzIsMX
|
|
81
80
|
mcp_proxy_adapter/core/server_adapter.py,sha256=jz8ztIfe82N5DE3XHRYpD6CwNcJy7ksh0l8l-towHBE,9755
|
82
81
|
mcp_proxy_adapter/core/server_engine.py,sha256=qmxdkBv-YsQsvxVVQ-_xiAyDshxtnrKBquPJoUjo2fw,9471
|
83
82
|
mcp_proxy_adapter/core/settings.py,sha256=D6cF4R_5gJ0XFGxzXUIzeqe-_muu6HL561TAei9wwZ0,10521
|
84
|
-
mcp_proxy_adapter/core/signal_handler.py,sha256=
|
83
|
+
mcp_proxy_adapter/core/signal_handler.py,sha256=TwBftsPAkMNcCSrE1ZA5qKnazcCVRpt_UGQzn6ITDHQ,5327
|
85
84
|
mcp_proxy_adapter/core/ssl_utils.py,sha256=Rjl79d5LdhDzxiMtaIRd9OFh0hTeRANItYFXk-7c5pA,9498
|
86
85
|
mcp_proxy_adapter/core/transport_manager.py,sha256=eJbGa3gDVFUBFUzMK5KEmpbUDXOOShtzszUIEf7Jk0A,9292
|
87
86
|
mcp_proxy_adapter/core/unified_config_adapter.py,sha256=zBGYdLDZ3G8f3Y9tmtm0Ne0UXIfEaNHR4Ik2W3ErkLc,22814
|
88
|
-
mcp_proxy_adapter/core/utils.py,sha256=
|
87
|
+
mcp_proxy_adapter/core/utils.py,sha256=StTL44fNXYcQfIwc68LTNTDgjyQ0feGCsYSBI0ly6qE,8047
|
89
88
|
mcp_proxy_adapter/examples/__init__.py,sha256=k1F-EotAFbJ3JvK_rNgiH4bUztmxIWtYn0AfbAZ1ZGs,450
|
90
89
|
mcp_proxy_adapter/examples/bugfix_certificate_config.py,sha256=YGBE_SI6wYZUJLWl7-fP1OWXiSH4mHJAZHApgQWvG7s,10529
|
91
90
|
mcp_proxy_adapter/examples/cert_manager_bugfix.py,sha256=UWUwItjqHqSnOMHocsz40_3deoZE8-vdROLW9y2fEns,7259
|
@@ -96,7 +95,6 @@ mcp_proxy_adapter/examples/create_test_configs.py,sha256=9TrvLa4-bWLPu0SB1JXwWuC
|
|
96
95
|
mcp_proxy_adapter/examples/debug_request_state.py,sha256=Z3Gy2-fWtu7KIV9OkzGDLVz7TpL_h9V_99ica40uQBU,4489
|
97
96
|
mcp_proxy_adapter/examples/debug_role_chain.py,sha256=GLVXC2fJUwP8UJnXHchd1t-H53cjWLJI3RqTPrKmaak,8750
|
98
97
|
mcp_proxy_adapter/examples/demo_client.py,sha256=en2Rtb70B1sQmhL-vdQ4PDpKNNl_mfll2YCFT_jFCAg,10191
|
99
|
-
mcp_proxy_adapter/examples/generate_certificates.py,sha256=cIfTHBziGiOTy9vldAmaULD6bXBpl2a5KfB8MLIRSww,16391
|
100
98
|
mcp_proxy_adapter/examples/generate_config.py,sha256=9zImMfIM88OQz12mE5k0_RnVV5ZmhlIKO2fRldiNCok,12709
|
101
99
|
mcp_proxy_adapter/examples/proxy_registration_example.py,sha256=vemRhftnjbiOBCJkmtDGqlWQ8syTG0a8755GCOnaQsg,12503
|
102
100
|
mcp_proxy_adapter/examples/required_certificates.py,sha256=YW9-V78oFiZ-FmHlGP-8FQFS569VdDVyq9hfvCv31pk,7133
|
@@ -117,12 +115,12 @@ mcp_proxy_adapter/examples/test_protocol_examples.py,sha256=yCZzZrJ9ICXMkF1bAMoz
|
|
117
115
|
mcp_proxy_adapter/examples/universal_client.py,sha256=n1-cBPOiCipA86Zcc_mI_jMywDMZS1p3u5JT3AqTsrQ,27577
|
118
116
|
mcp_proxy_adapter/examples/update_config_certificates.py,sha256=ObGF5oNQ9OStryUvFDXxrN-olRMKdSOrl5KSwARF7EY,4608
|
119
117
|
mcp_proxy_adapter/examples/basic_framework/__init__.py,sha256=4aYD--R6hy9n9CUxj7Osb9HcdVUMJ6_cfpu4ujkbCwI,345
|
120
|
-
mcp_proxy_adapter/examples/basic_framework/main.py,sha256=
|
118
|
+
mcp_proxy_adapter/examples/basic_framework/main.py,sha256=2bpT-zrekpzzbhOx6wOOYXYE1mejWXkT617zpSN7m70,1790
|
121
119
|
mcp_proxy_adapter/examples/basic_framework/commands/__init__.py,sha256=_VQNLUEdsxUG-4yt9BZI_vtOxHAdGG0OUSsP6Wj-Vz4,76
|
122
120
|
mcp_proxy_adapter/examples/basic_framework/hooks/__init__.py,sha256=IE_EIXMnkdXuakZn7wLD9kBFyfDF5lYi56ejgiBeb-A,70
|
123
121
|
mcp_proxy_adapter/examples/commands/__init__.py,sha256=zvY_OpH_B1bVc_khrNIl6O8vqCw1FH6gGMAsJAkGWGY,170
|
124
122
|
mcp_proxy_adapter/examples/full_application/__init__.py,sha256=xGiPYhRAzs1Fh9wA8HoowV-Gg9QMLaMZn-OamExq1TI,320
|
125
|
-
mcp_proxy_adapter/examples/full_application/main.py,sha256=
|
123
|
+
mcp_proxy_adapter/examples/full_application/main.py,sha256=ZJmXe6WU-4q-U0AKIAdDm9HNREBgWwZz26ohY511xHs,5603
|
126
124
|
mcp_proxy_adapter/examples/full_application/proxy_endpoints.py,sha256=Kt_WAsG61HLTMkKQ1mQqjvlX9I4TcfwYq0NaRR9HKvM,6179
|
127
125
|
mcp_proxy_adapter/examples/full_application/commands/__init__.py,sha256=yQHxVSFkAyFLUOdk42QOebUODPlQV9IbydPgF3UKsGM,217
|
128
126
|
mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py,sha256=H7FPJmVJNWT61rPWxep06-7hsYRt8XYBUSBiwqpBurU,3096
|
@@ -134,8 +132,8 @@ mcp_proxy_adapter/schemas/base_schema.json,sha256=v9G9cGMd4dRhCZsOQ_FMqOi5VFyVbI
|
|
134
132
|
mcp_proxy_adapter/schemas/openapi_schema.json,sha256=C3yLkwmDsvnLW9B5gnKKdBGl4zxkeU-rEmjTrNVsQU0,8405
|
135
133
|
mcp_proxy_adapter/schemas/roles.json,sha256=pgf_ZyqKyXbfGUxvobpiLiSJz9zzxrMuoVWEkEpz3N8,764
|
136
134
|
mcp_proxy_adapter/schemas/roles_schema.json,sha256=deHgI7L6GwfBXacOlNtDgDJelDThppClC3Ti4Eh8rJY,5659
|
137
|
-
mcp_proxy_adapter-6.
|
138
|
-
mcp_proxy_adapter-6.
|
139
|
-
mcp_proxy_adapter-6.
|
140
|
-
mcp_proxy_adapter-6.
|
141
|
-
mcp_proxy_adapter-6.
|
135
|
+
mcp_proxy_adapter-6.8.0.dist-info/METADATA,sha256=PX886Dr7QaN1S-XzB0MiNXHgp53l5XFGpLGeGRnjgPs,8510
|
136
|
+
mcp_proxy_adapter-6.8.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
137
|
+
mcp_proxy_adapter-6.8.0.dist-info/entry_points.txt,sha256=Bf-O5Aq80n22Ayu9fI9BgidzWqwzIVaqextAddTuHZw,563
|
138
|
+
mcp_proxy_adapter-6.8.0.dist-info/top_level.txt,sha256=JZT7vPLBYrtroX-ij68JBhJYbjDdghcV-DFySRy-Nnw,18
|
139
|
+
mcp_proxy_adapter-6.8.0.dist-info/RECORD,,
|