#!/usr/bin/env python3
"""Main entry point for 42crunch MCP Server."""
import argparse
import sys
import os
import signal
import atexit
from pathlib import Path
from src.server import mcp
def daemonize(pidfile=None, workdir=None):
"""Daemonize the current process.
Args:
pidfile: Path to PID file (optional)
workdir: Working directory (optional)
"""
# Fork the first child
try:
pid = os.fork()
if pid > 0:
# Exit parent process
sys.exit(0)
except OSError as e:
sys.stderr.write(f"Fork #1 failed: {e}\n")
sys.exit(1)
# Decouple from parent environment
os.chdir(workdir or "/")
os.setsid()
os.umask(0)
# Fork the second child
try:
pid = os.fork()
if pid > 0:
# Exit from second parent
sys.exit(0)
except OSError as e:
sys.stderr.write(f"Fork #2 failed: {e}\n")
sys.exit(1)
# Redirect standard file descriptors
sys.stdout.flush()
sys.stderr.flush()
si = open(os.devnull, 'r')
so = open(os.devnull, 'a+')
se = open(os.devnull, 'a+')
os.dup2(si.fileno(), sys.stdin.fileno())
os.dup2(so.fileno(), sys.stdout.fileno())
os.dup2(se.fileno(), sys.stderr.fileno())
# Write PID file
if pidfile:
pid = str(os.getpid())
with open(pidfile, 'w+') as f:
f.write(f"{pid}\n")
# Register cleanup function
def remove_pidfile():
try:
os.remove(pidfile)
except OSError:
pass
atexit.register(remove_pidfile)
def main():
"""Main entry point with command-line options."""
parser = argparse.ArgumentParser(
description="42crunch MCP Server - JSON-RPC server for 42crunch API",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
# Run in foreground (default)
python main.py
# Run as daemon in background (PID file in project directory)
python main.py --daemon --pidfile ./42crunch-mcp.pid
# Run with custom PID file location
python main.py --daemon --pidfile /path/to/custom.pid
# Run with custom working directory
python main.py --daemon --pidfile ./42crunch-mcp.pid --workdir /opt/42crunch-mcp
"""
)
parser.add_argument(
"--daemon",
action="store_true",
help="Run as daemon in background"
)
parser.add_argument(
"--pidfile",
type=str,
help="Path to PID file (required with --daemon)"
)
parser.add_argument(
"--workdir",
type=str,
help="Working directory for daemon (default: /)"
)
args = parser.parse_args()
# Validate daemon options
if args.daemon and not args.pidfile:
parser.error("--pidfile is required when using --daemon")
# Daemonize if requested
if args.daemon:
daemonize(pidfile=args.pidfile, workdir=args.workdir)
# Run the MCP server
try:
mcp.run()
except KeyboardInterrupt:
print("\nShutting down...")
sys.exit(0)
except Exception as e:
sys.stderr.write(f"Error: {e}\n")
sys.exit(1)
if __name__ == "__main__":
main()