@debugmcp/mcp-debugger
Version:
Step-through debugging MCP server for LLMs
116 lines (96 loc) • 4.24 kB
JavaScript
/* eslint-disable no-console */
// This file runs in the proxy process before TypeScript types are available.
// console.error is used intentionally for debugging proxy startup.
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { shouldExitAsOrphanFromEnv } from './utils/orphan-check.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const bootstrapLogPrefix = `[Bootstrap ${new Date().toISOString()}]`;
// Simple logging function - just use stderr
function logBootstrapActivity(message) {
console.error(`${bootstrapLogPrefix} ${message}`);
}
// Set up signal handlers to ensure proper cleanup
process.on('SIGTERM', () => {
logBootstrapActivity('Received SIGTERM, shutting down gracefully...');
process.exit(0);
});
process.on('SIGINT', () => {
logBootstrapActivity('Received SIGINT, shutting down gracefully...');
process.exit(0);
});
// Handle parent process death
process.on('disconnect', () => {
logBootstrapActivity('Parent process disconnected, shutting down...');
process.exit(0);
});
// Set up heartbeat to detect orphaned state
let lastHeartbeat = Date.now();
const HEARTBEAT_TIMEOUT = 30000; // 30 seconds
// Check if we're orphaned every 10 seconds
setInterval(() => {
// Use container-safe orphan detection (ppid=1 ignored inside containers)
if (shouldExitAsOrphanFromEnv(process.ppid, process.env)) {
logBootstrapActivity('Process orphaned (ppid=1 outside container), terminating...');
process.exit(1);
}
// Also check if we haven't received any IPC messages in a while
if (Date.now() - lastHeartbeat > HEARTBEAT_TIMEOUT && process.send) {
try {
// Try to ping parent
process.send({ type: 'heartbeat', pid: process.pid });
} catch {
logBootstrapActivity('Cannot communicate with parent, terminating...');
process.exit(1);
}
}
}, 10000);
// Update heartbeat on any message from parent
process.on('message', () => {
lastHeartbeat = Date.now();
});
logBootstrapActivity(`Bootstrap script started. CWD: ${process.cwd()}`);
(async () => {
try {
// Set environment variable to explicitly signal proxy mode
process.env.DAP_PROXY_WORKER = 'true';
logBootstrapActivity('Setting DAP_PROXY_WORKER environment variable to indicate proxy mode.');
// Determine which proxy version to load
const bundlePath = path.join(__dirname, 'proxy-bundle.cjs');
const entryPath = path.join(__dirname, 'dap-proxy-entry.js');
// Simply check if bundle exists - prefer it when available for reliability
const useBundle = fs.existsSync(bundlePath);
const proxyPath = useBundle ? bundlePath : entryPath;
logBootstrapActivity(`Using ${useBundle ? 'bundled' : 'unbundled'} proxy from: ${proxyPath}`);
// Verify the chosen file exists
if (!fs.existsSync(proxyPath)) {
logBootstrapActivity(`ERROR: Proxy file not found at ${proxyPath}`);
if (useBundle) {
logBootstrapActivity('Bundle was expected but not found. Build may have failed.');
}
process.exit(1);
}
// Convert to file URL for ESM import
// On Windows: file:///C:/path/to/file
// On Unix: file:///path/to/file
const normalizedPath = proxyPath.replace(/\\/g, '/');
const proxyUrl = normalizedPath.startsWith('/')
? `file://${normalizedPath}` // Unix path already has leading slash
: `file:///${normalizedPath}`; // Windows path needs three slashes
logBootstrapActivity(`Importing proxy from URL: ${proxyUrl}`);
try {
await import(proxyUrl);
logBootstrapActivity(`Dynamic import of ${useBundle ? 'bundled' : 'unbundled'} proxy succeeded.`);
} catch (importError) {
const errorMessage = importError instanceof Error ? `${importError.name}: ${importError.message}\n${importError.stack}` : String(importError);
logBootstrapActivity(`ERROR during dynamic import of proxy: ${errorMessage}`);
throw importError;
}
} catch (e) {
const errorMessage = e instanceof Error ? `${e.name}: ${e.message}\n${e.stack}` : String(e);
logBootstrapActivity(`ERROR during proxy bootstrap: ${errorMessage}`);
process.exit(1);
}
})();