141 lines
4.3 KiB
Python
141 lines
4.3 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
MCP Browser Daemon - Socket server for MCP Browser.
|
|
|
|
This daemon provides a persistent MCP Browser instance that clients can connect to.
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import asyncio
|
|
import argparse
|
|
import signal
|
|
from pathlib import Path
|
|
from typing import Optional
|
|
|
|
from .proxy import MCPBrowser
|
|
from .daemon import MCPBrowserDaemon, get_socket_path
|
|
from .logging_config import setup_logging, get_logger
|
|
|
|
|
|
async def run_daemon(args):
|
|
"""Run the MCP Browser daemon."""
|
|
logger = get_logger(__name__)
|
|
|
|
# Create browser instance
|
|
browser = MCPBrowser(
|
|
server_name=args.server,
|
|
config_path=Path(args.config) if args.config else None,
|
|
enable_builtin_servers=not args.no_builtin
|
|
)
|
|
|
|
# Get socket path
|
|
socket_path = get_socket_path(args.server)
|
|
|
|
# Create and run daemon
|
|
daemon = MCPBrowserDaemon(browser, socket_path)
|
|
|
|
logger.info(f"Starting MCP Browser daemon on {socket_path}")
|
|
|
|
try:
|
|
await daemon.start()
|
|
except KeyboardInterrupt:
|
|
logger.info("Daemon shutting down...")
|
|
except Exception as e:
|
|
logger.error(f"Daemon error: {e}")
|
|
raise
|
|
finally:
|
|
await daemon.stop()
|
|
|
|
|
|
def handle_systemd_socket():
|
|
"""Check for systemd socket activation."""
|
|
# Check if we're running under systemd with socket activation
|
|
listen_pid = os.environ.get('LISTEN_PID')
|
|
listen_fds = os.environ.get('LISTEN_FDS')
|
|
|
|
if listen_pid and listen_fds and int(listen_pid) == os.getpid():
|
|
# We have systemd socket activation
|
|
num_fds = int(listen_fds)
|
|
if num_fds > 0:
|
|
# Use the first socket FD (SD_LISTEN_FDS_START = 3)
|
|
return 3
|
|
return None
|
|
|
|
|
|
def main():
|
|
"""Main entry point for daemon."""
|
|
parser = argparse.ArgumentParser(
|
|
description="MCP Browser Daemon - Persistent socket server",
|
|
formatter_class=argparse.RawDescriptionHelpFormatter
|
|
)
|
|
|
|
parser.add_argument("--server", "-s", help="Target MCP server name")
|
|
parser.add_argument("--config", "-c", help="Custom configuration file path")
|
|
parser.add_argument("--no-builtin", action="store_true",
|
|
help="Disable built-in servers")
|
|
parser.add_argument("--log-level", choices=["TRACE", "DEBUG", "INFO", "WARNING", "ERROR"],
|
|
help="Set logging level")
|
|
parser.add_argument("--log-file", help="Log to file instead of stderr")
|
|
parser.add_argument("--socket", help="Custom socket path (overrides default)")
|
|
parser.add_argument("--foreground", "-f", action="store_true",
|
|
help="Run in foreground (don't daemonize)")
|
|
parser.add_argument("--pid-file", help="Write PID to file")
|
|
|
|
args = parser.parse_args()
|
|
|
|
# Setup logging
|
|
log_file = Path(args.log_file) if args.log_file else None
|
|
setup_logging(
|
|
debug=args.log_level == "DEBUG",
|
|
log_file=log_file,
|
|
log_level=args.log_level
|
|
)
|
|
|
|
logger = get_logger(__name__)
|
|
|
|
# Check for systemd socket activation
|
|
systemd_fd = handle_systemd_socket()
|
|
if systemd_fd:
|
|
logger.info("Running with systemd socket activation")
|
|
# TODO: Implement systemd socket handling
|
|
|
|
# Handle PID file
|
|
if args.pid_file:
|
|
with open(args.pid_file, 'w') as f:
|
|
f.write(str(os.getpid()))
|
|
|
|
# Daemonize if not in foreground mode
|
|
if not args.foreground:
|
|
# Fork to background
|
|
pid = os.fork()
|
|
if pid > 0:
|
|
# Parent process
|
|
print(f"Started daemon with PID {pid}")
|
|
sys.exit(0)
|
|
|
|
# Child process continues
|
|
os.setsid()
|
|
|
|
# Redirect stdin/stdout/stderr
|
|
with open(os.devnull, 'r') as devnull:
|
|
os.dup2(devnull.fileno(), sys.stdin.fileno())
|
|
with open(os.devnull, 'w') as devnull:
|
|
os.dup2(devnull.fileno(), sys.stdout.fileno())
|
|
if not args.log_file:
|
|
os.dup2(devnull.fileno(), sys.stderr.fileno())
|
|
|
|
# Set up signal handlers
|
|
def signal_handler(signum, frame):
|
|
logger.info(f"Received signal {signum}")
|
|
asyncio.create_task(daemon.stop())
|
|
|
|
signal.signal(signal.SIGTERM, signal_handler)
|
|
signal.signal(signal.SIGINT, signal_handler)
|
|
|
|
# Run the daemon
|
|
asyncio.run(run_daemon(args))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main() |