import asyncio
import signal
import sys
import threading
import time
import uvicorn
from logging_setup import get_logger
from config import ServerConfig
from api import create_app
from mcp_tools import mcp
logger = get_logger(__name__)
class MCPServer:
"""MCP Server with support for multiple transports."""
def __init__(self, config: ServerConfig):
self.config = config
self.http_thread = None
self.http_server = None
self.shutdown_requested = False
# Create the combined app
try:
self.app = create_app(mcp, config.storage_path)
logger.info(f"Created app with storage path: {config.storage_path}")
except Exception as e:
logger.error(f"Failed to create app: {e}")
raise
def setup_signal_handlers(self):
"""Setup signal handlers for graceful shutdown."""
def signal_handler(sig, frame):
logger.info(f"Received signal {sig}, initiating graceful shutdown...")
self.shutdown_requested = True
self.shutdown()
sys.exit(0)
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
def run_http_server(self):
"""Run the HTTP server using uvicorn with proper shutdown handling."""
try:
logger.info(f"Starting HTTP server on {self.config.host}:{self.config.port}")
# Create uvicorn server configuration
config = uvicorn.Config(
self.app,
host=self.config.host,
port=self.config.port,
log_level="info"
)
# Create server instance
self.http_server = uvicorn.Server(config)
# Run the server
asyncio.run(self.http_server.serve())
except Exception as e:
logger.error(f"HTTP server failed: {e}")
if not self.shutdown_requested:
raise
def start_http_server_thread(self):
"""Start HTTP server in a background thread."""
self.http_thread = threading.Thread(target=self.run_http_server, daemon=True)
self.http_thread.start()
# Give the server a moment to start
time.sleep(1)
logger.info(f"HTTP server thread started on {self.config.host}:{self.config.port}")
def run_stdio_server(self):
"""Run the stdio MCP server."""
try:
logger.info("Starting MCP stdio server...")
mcp.run()
except Exception as e:
logger.error(f"MCP stdio server failed: {e}")
if not self.shutdown_requested:
raise
def run(self):
"""Run the server with the configured transports."""
logger.info(f"Using transports: {', '.join(sorted(self.config.transports))}")
logger.info(f"Configuration: port={self.config.port}, host={self.config.host}, storage_path={self.config.storage_path}")
self.setup_signal_handlers()
try:
if {"stdio", "http"}.issubset(self.config.transports):
# Dual transport mode
logger.info("Running in dual transport mode (stdio + http)")
self.start_http_server_thread()
self.run_stdio_server()
elif "http" in self.config.transports:
# HTTP only mode
logger.info("Running in HTTP-only mode")
self.run_http_server()
elif "stdio" in self.config.transports:
# Stdio only mode
logger.info("Running in stdio-only mode")
self.run_stdio_server()
else:
logger.warning("No valid transports configured")
except KeyboardInterrupt:
logger.info("Received keyboard interrupt")
except Exception as e:
logger.error(f"Server error: {e}")
raise
finally:
self.shutdown()
def shutdown(self):
"""Perform cleanup on shutdown."""
logger.info("Shutting down server...")
self.shutdown_requested = True
# Shutdown HTTP server if running
if self.http_server:
logger.info("Shutting down HTTP server...")
self.http_server.should_exit = True
# Give it a moment to shutdown gracefully
time.sleep(0.5)
if self.http_thread and self.http_thread.is_alive():
logger.info("Waiting for HTTP server thread to finish...")
self.http_thread.join(timeout=5.0)
if self.http_thread.is_alive():
logger.warning("HTTP server thread did not terminate gracefully")