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

Class SocketTransport

src/mcp/transport.ts:344–436  ·  view source on GitHub ↗

Source from the content-addressed store, hash-verified

342 * ending must not bring down the whole daemon.
343 */
344export class SocketTransport extends LineBasedJsonRpcTransport {
345 private buffer = '';
346 private closeHandlers: Array<() => void> = [];
347
348 constructor(private socket: Socket, private prefix: string = 'cg-sock') {
349 super();
350 }
351
352 /**
353 * Register a callback fired exactly once when the socket closes (from either
354 * side). Used by the daemon to decrement its connected-clients refcount.
355 */
356 onClose(handler: () => void): void {
357 this.closeHandlers.push(handler);
358 }
359
360 start(handler: MessageHandler): void {
361 this.messageHandler = handler;
362
363 this.socket.setEncoding('utf8');
364 if (process.env.CODEGRAPH_MCP_DEBUG) {
365 process.stderr.write(`[mcp-debug] transport attached flowing=${String(this.socket.readableFlowing)} buffered=${this.socket.readableLength}\n`);
366 }
367 this.socket.on('data', (chunk: string) => {
368 if (process.env.CODEGRAPH_MCP_DEBUG) process.stderr.write(`[mcp-debug] transport data ${chunk.length}b\n`);
369 this.buffer += chunk;
370 let idx;
371 // Drain every complete line; tail-fragment stays in the buffer for the
372 // next chunk. The handler is async but we don't await it here — JSON-RPC
373 // permits out-of-order responses, and serializing here would deadlock if
374 // a handler issued a server-initiated request that needed a *later* line
375 // to arrive (e.g. roots/list mid-tools-call).
376 while ((idx = this.buffer.indexOf('\n')) !== -1) {
377 const line = this.buffer.slice(0, idx);
378 this.buffer = this.buffer.slice(idx + 1);
379 void this.handleLine(line);
380 }
381 });
382
383 this.socket.on('close', () => this.handleSocketClose());
384 this.socket.on('error', (err) => {
385 // Don't crash the daemon over a broken pipe; just shut this connection.
386 process.stderr.write(`[CodeGraph daemon] socket error: ${err.message}\n`);
387 this.handleSocketClose();
388 });
389 // The daemon's hello reader hands the socket over PAUSED (so the unshifted
390 // tail can't be emitted to zero listeners and lost — the #662 wedge).
391 // Attaching 'data' does not resume an explicitly-paused stream; do it here.
392 // Harmless when the socket was never paused.
393 this.socket.resume();
394 }
395
396 stop(): void {
397 if (this.stopped) return;
398 this.stopped = true;
399 this.rejectPending('Transport stopped');
400 if (!this.socket.destroyed) {
401 this.socket.end();

Callers

nothing calls this directly

Calls

no outgoing calls

Tested by

no test coverage detected