{"record":{"id":"fd95298546ff23fe","repo":"abhigyanpatwari/GitNexus","slug":"missing-content-length-header-from-mcp-client","errorCode":null,"errorMessage":"Missing Content-Length header from MCP client","messagePattern":"Missing Content-Length header from MCP client","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"gitnexus/src/mcp/compatible-stdio-transport.ts","lineNumber":146,"sourceCode":"\n  private readContentLengthMessage(): JSONRPCMessage | null {\n    if (!this._readBuffer) {\n      return null;\n    }\n\n    const header = findHeaderEnd(this._readBuffer);\n    if (header === null) {\n      return null;\n    }\n\n    const headerText = this._readBuffer\n      .toString('utf8', 0, header.index)\n      .replace(/\\r\\n/g, '\\n')\n      .replace(/\\r/g, '\\n');\n    const match = headerText.match(/(?:^|\\n)content-length\\s*:\\s*(\\d+)/i);\n    if (!match) {\n      this.discardBufferedInput();\n      throw new Error('Missing Content-Length header from MCP client');\n    }\n\n    const contentLength = Number.parseInt(match[1], 10);\n    if (!Number.isFinite(contentLength) || contentLength < 0) {\n      this.discardBufferedInput();\n      throw new Error('Invalid Content-Length header from MCP client');\n    }\n    if (contentLength > MAX_BUFFER_SIZE) {\n      this.discardBufferedInput();\n      throw new Error(\n        `Content-Length ${contentLength} exceeds maximum allowed size (${MAX_BUFFER_SIZE} bytes)`,\n      );\n    }\n    const bodyStart = header.index + header.separatorLength;\n    const bodyEnd = bodyStart + contentLength;\n    if (this._readBuffer.length < bodyEnd) {\n      return null;\n    }","sourceCodeStart":128,"sourceCodeEnd":164,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/aac7515d2a8c50a1f8f923c6fb77218b333560d6/gitnexus/src/mcp/compatible-stdio-transport.ts#L128-L164","documentation":"The compatible stdio transport reads LSP-style framed JSON-RPC: a header block terminated by a blank line followed by a Content-Length-delimited body. After locating the header terminator, it regex-matches for a content-length header; when no match exists it discards all buffered input and throws, because the stream is not speaking the expected framing protocol.","triggerScenarios":"An MCP client writes raw newline-delimited JSON to GitNexus's stdin instead of Content-Length framed messages; a hand-rolled client writes plain JSON.stringify(msg) + '\\n'; a client accidentally interleaves log/debug output into its stdout (GitNexus's stdin) ahead of a real message; a proxy or wrapper mangles the byte stream.","commonSituations":"Custom MCP clients not built on the official SDK; confusion between NDJSON-based transports and the LSP framing the SDK uses; debug prints accidentally left in a client's stdout path; integrating GitNexus MCP behind a homemade pipe/spawn wrapper that buffers or rewrites frames.","solutions":["Use the official MCP SDK client (TypeScript/Python), which performs Content-Length framing correctly.","If hand-rolling, write `Content-Length: <utf8 byte length>\\r\\n\\r\\n` before each JSON body — measure bytes, not string length.","Remove any stray console.log/print to stdout in the client; route client logs to stderr.","Restart the MCP session after this throw: discardBufferedInput() has already dropped pending bytes, so the stream state is unrecoverable."],"exampleFix":"// before: unframed NDJSON (throws Missing Content-Length header)\nprocess.stdout.write(JSON.stringify(rpcRequest) + '\\n');\n\n// after: LSP-style framing (what the SDK does)\nconst body = Buffer.from(JSON.stringify(rpcRequest), 'utf8');\nprocess.stdout.write(`Content-Length: ${body.length}\\r\\n\\r\\n`);\nprocess.stdout.write(body);","handlingStrategy":"validation","validationCode":"// Client-side: only write properly framed messages, never raw lines\nimport * as net from 'node:net';\n\nfunction writeFrame(sock: net.Socket, msg: unknown): void {\n  const body = Buffer.from(JSON.stringify(msg), 'utf8');\n  if (body.length === 0) throw new Error('refusing to send empty frame');\n  sock.write(`Content-Length: ${body.length}\\r\\n\\r\\n`);\n  sock.write(body);\n}","typeGuard":null,"tryCatchPattern":"// Server-side: a framing error means the byte stream is unrecoverable (input was discarded)\ntry {\n  transport.processReadData();\n} catch (err) {\n  if (err instanceof Error && err.message.startsWith('Missing Content-Length')) {\n    log.error('client speaks unframed stdio — closing session', { err });\n    await transport.close(); // do NOT reuse the session\n    return;\n  }\n  throw err;\n}","preventionTips":["Use the official MCP SDK client transports instead of hand-rolled framing.","Never write logs or debug output to a client's stdout — that pipe IS the server's stdin; use stderr.","Compute Content-Length from Buffer.byteLength of the serialized body, not string length.","Add an integration test that round-trips one request through the real stdio transport before shipping a custom client."],"tags":["mcp","stdio-transport","content-length","framing","json-rpc","protocol"],"backgroundTag":"malformed-message-framing","analyzedSha":"aac7515d2a8c50a1f8f923c6fb77218b333560d6","analyzedAt":"2026-08-20T23:29:22.980Z","schemaVersion":2},"datasetVersion":"2026-08-22T20:17:22.307Z"}