* Start the MCP server. * * Decision order: * 1. `CODEGRAPH_NO_DAEMON=1` → direct mode (unchanged pre-#411 behavior). * 2. `CODEGRAPH_DAEMON_INTERNAL=1` → we ARE the detached daemon; listen. * 3. No `.codegraph/` reachable → direct mode (the daemon's lockfile and * socke
()
| 215 | * mode — a misbehaving daemon must never block a session from starting. |
| 216 | */ |
| 217 | async start(): Promise<void> { |
| 218 | // Long-lived process (direct / proxy / daemon alike): flush buffered |
| 219 | // telemetry opportunistically. Fire-and-forget + unref'd — adds nothing |
| 220 | // to the handshake path and never keeps the process alive. |
| 221 | getTelemetry().startInterval(); |
| 222 | |
| 223 | // The detached daemon process itself. Checked before the opt-out so the |
| 224 | // daemon honors the same env it was spawned with (it never sets NO_DAEMON). |
| 225 | if (daemonInternalSet()) { |
| 226 | return this.startDaemonProcess(); |
| 227 | } |
| 228 | |
| 229 | // Direct mode if the user opted out. Setting the env var is sufficient to |
| 230 | // get the pre-#411 single-process behavior. |
| 231 | if (daemonOptOutSet()) { |
| 232 | return this.startDirect('CODEGRAPH_NO_DAEMON set'); |
| 233 | } |
| 234 | |
| 235 | const root = resolveDaemonRoot(this.projectPath); |
| 236 | if (!root) { |
| 237 | // No initialized project found — daemon mode has nowhere to put its |
| 238 | // socket. The fresh-checkout / outside-project case; behave as before. |
| 239 | return this.startDirect('no .codegraph/ root found'); |
| 240 | } |
| 241 | |
| 242 | try { |
| 243 | // Answer the MCP handshake LOCALLY (instant tool registration — no waiting |
| 244 | // ~600ms for the daemon to spawn+bind, which produced the cold-start race) |
| 245 | // and forward tool CALLS to the shared daemon, connected in the background. |
| 246 | // Runs until the host disconnects; the proxy installs its own watchdog and |
| 247 | // falls back to an in-process engine if the daemon never comes up. |
| 248 | this.mode = 'proxy'; |
| 249 | await this.runProxyWithLocalHandshake(root); |
| 250 | return; |
| 251 | } catch (err) { |
| 252 | // Belt-and-braces: a throw during proxy SETUP (before the client was served) |
| 253 | // is still safe to recover from with a direct-mode session. |
| 254 | const msg = err instanceof Error ? err.message : String(err); |
| 255 | process.stderr.write(`[CodeGraph MCP] Proxy path failed (${msg}); falling back to direct mode.\n`); |
| 256 | return this.startDirect('proxy path threw'); |
| 257 | } |
| 258 | } |
| 259 | |
| 260 | /** |
| 261 | * Stop the server. In daemon mode this triggers graceful shutdown of every |
no test coverage detected