Single-process stdio MCP session — the pre-issue-#411 code path.
(reason: string)
| 349 | |
| 350 | /** Single-process stdio MCP session — the pre-issue-#411 code path. */ |
| 351 | private async startDirect(reason: string): Promise<void> { |
| 352 | if (reason && process.env.CODEGRAPH_MCP_DEBUG) { |
| 353 | process.stderr.write(`[CodeGraph MCP] Direct mode: ${reason}.\n`); |
| 354 | } |
| 355 | this.engine = new MCPEngine(); |
| 356 | const transport = new StdioTransport(); |
| 357 | this.session = new MCPSession(transport, this.engine, { |
| 358 | explicitProjectPath: this.projectPath, |
| 359 | }); |
| 360 | |
| 361 | if (this.projectPath) { |
| 362 | // Background init so the initialize response stays fast (#172). |
| 363 | void this.engine.ensureInitialized(this.projectPath); |
| 364 | } |
| 365 | |
| 366 | this.session.start(); |
| 367 | |
| 368 | // Detect parent-process death — same logic as pre-refactor. When stdin |
| 369 | // closes we go through StdioTransport's `process.exit(0)` already, but |
| 370 | // SIGKILL of the parent doesn't reliably close stdin on Linux (#277). |
| 371 | // Also treat a stdin `'error'` (a socket-backed stdin can fail with |
| 372 | // ECONNRESET/hangup instead of a clean close) as shutdown, and destroy the |
| 373 | // stream so a hung fd can't busy-spin the event loop (#799). |
| 374 | treatStdinFailureAsShutdown(() => this.stop()); |
| 375 | // Backstop for a launch abandoned during startup (#1185): launcher killed |
| 376 | // before EARLY_PPID could see it + host holding our pipes open. A server |
| 377 | // that never receives a byte of MCP traffic isn't serving anyone. Armed |
| 378 | // after session.start() attached the real stdin consumer. |
| 379 | armStartupHandshakeTimeout(() => { |
| 380 | process.stderr.write( |
| 381 | '[CodeGraph MCP] No MCP traffic since startup; assuming an abandoned launch and shutting down (#1185). ' + |
| 382 | 'Tune with CODEGRAPH_STARTUP_HANDSHAKE_TIMEOUT_MS (0 disables).\n' |
| 383 | ); |
| 384 | this.stop(); |
| 385 | }); |
| 386 | |
| 387 | this.mode = 'direct'; |
| 388 | this.installSignalHandlers(); |
| 389 | this.installPpidWatchdog(); |
| 390 | this.livenessWatchdog = installMainThreadWatchdog(watchdogProgressPaths(resolveDaemonRoot(this.projectPath))); |
| 391 | } |
| 392 | |
| 393 | /** |
| 394 | * Run as the detached shared daemon (process spawned with |
no test coverage detected