MCPcopy Create free account
hub / github.com/colbymchenry/codegraph / MCPServer

Class MCPServer

src/mcp/index.ts:230–518  ·  view source on GitHub ↗

Source from the content-addressed store, hash-verified

228 * daemon at start time.
229 */
230export class MCPServer {
231 private projectPath: string | null;
232 // Direct-mode-only state. In daemon mode the per-connection sessions live
233 // inside the Daemon class; in proxy mode there is no session at all.
234 private session: MCPSession | null = null;
235 private engine: MCPEngine | null = null;
236 private daemon: Daemon | null = null;
237 private ppidWatchdog: ReturnType<typeof setInterval> | null = null;
238 // Worker-thread liveness watchdog (#850). Long-lived modes only; SIGKILLs the
239 // process if the main thread wedges in a non-yielding sync loop.
240 private livenessWatchdog: WatchdogHandle | null = null;
241 // PPID watchdog baseline — from the CLI entry's earliest-possible capture
242 // (early-ppid.ts). Capturing here (construction) already lost the race when
243 // the launcher was killed during module loading (#1185).
244 private originalPpid: number = EARLY_PPID;
245 private hostPpid: number | null = parseHostPpid(process.env[HOST_PPID_ENV]);
246 // Idempotency guard for stop().
247 private stopped = false;
248 private mode: 'unstarted' | 'direct' | 'proxy' | 'daemon' = 'unstarted';
249
250 constructor(projectPath?: string) {
251 this.projectPath = projectPath || null;
252 }
253
254 /**
255 * Start the MCP server.
256 *
257 * Decision order:
258 * 1. `CODEGRAPH_NO_DAEMON=1` → direct mode (unchanged pre-#411 behavior).
259 * 2. `CODEGRAPH_DAEMON_INTERNAL=1` → we ARE the detached daemon; listen.
260 * 3. No `.codegraph/` reachable → direct mode (the daemon's lockfile and
261 * socket both live under `.codegraph/`).
262 * 4. Otherwise connect to (or spawn) the shared daemon and proxy to it.
263 *
264 * On any unexpected failure in step 4 we transparently fall back to direct
265 * mode — a misbehaving daemon must never block a session from starting.
266 */
267 async start(): Promise<void> {
268 // Long-lived process (direct / proxy / daemon alike): flush buffered
269 // telemetry opportunistically. Fire-and-forget + unref'd — adds nothing
270 // to the handshake path and never keeps the process alive.
271 getTelemetry().startInterval();
272
273 // #1243: the MCP config launches the local binary, so a server left
274 // running drifts behind releases with no signal. Refresh the shared
275 // update-check cache in the background and log ONE stderr notice when a
276 // newer version exists (stderr only — stdout is the protocol channel).
277 // The notice also reaches the agent via the initialize instructions and
278 // codegraph_status. Fire-and-forget: adds nothing to the handshake path.
279 checkForUpdateInBackground();
280
281 // The detached daemon process itself. Checked before the opt-out so the
282 // daemon honors the same env it was spawned with (it never sets NO_DAEMON).
283 if (daemonInternalSet()) {
284 return this.startDaemonProcess();
285 }
286
287 // Direct mode if the user opted out. Setting the env var is sufficient to

Callers

nothing calls this directly

Calls 1

parseHostPpidFunction · 0.90

Tested by

no test coverage detected