abhigyanpatwari/GitNexus · error · Error
Missing Content-Length header from MCP client
Error message
Missing Content-Length header from MCP client
What it means
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.
Source
Thrown at gitnexus/src/mcp/compatible-stdio-transport.ts:146
private readContentLengthMessage(): JSONRPCMessage | null {
if (!this._readBuffer) {
return null;
}
const header = findHeaderEnd(this._readBuffer);
if (header === null) {
return null;
}
const headerText = this._readBuffer
.toString('utf8', 0, header.index)
.replace(/\r\n/g, '\n')
.replace(/\r/g, '\n');
const match = headerText.match(/(?:^|\n)content-length\s*:\s*(\d+)/i);
if (!match) {
this.discardBufferedInput();
throw new Error('Missing Content-Length header from MCP client');
}
const contentLength = Number.parseInt(match[1], 10);
if (!Number.isFinite(contentLength) || contentLength < 0) {
this.discardBufferedInput();
throw new Error('Invalid Content-Length header from MCP client');
}
if (contentLength > MAX_BUFFER_SIZE) {
this.discardBufferedInput();
throw new Error(
`Content-Length ${contentLength} exceeds maximum allowed size (${MAX_BUFFER_SIZE} bytes)`,
);
}
const bodyStart = header.index + header.separatorLength;
const bodyEnd = bodyStart + contentLength;
if (this._readBuffer.length < bodyEnd) {
return null;
}View on GitHub (pinned to aac7515d2a)
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.
Example fix
// before: unframed NDJSON (throws Missing Content-Length header)
process.stdout.write(JSON.stringify(rpcRequest) + '\n');
// after: LSP-style framing (what the SDK does)
const body = Buffer.from(JSON.stringify(rpcRequest), 'utf8');
process.stdout.write(`Content-Length: ${body.length}\r\n\r\n`);
process.stdout.write(body); Defensive patterns
Strategy: validation
Validate before calling
// Client-side: only write properly framed messages, never raw lines
import * as net from 'node:net';
function writeFrame(sock: net.Socket, msg: unknown): void {
const body = Buffer.from(JSON.stringify(msg), 'utf8');
if (body.length === 0) throw new Error('refusing to send empty frame');
sock.write(`Content-Length: ${body.length}\r\n\r\n`);
sock.write(body);
} Try / catch
// Server-side: a framing error means the byte stream is unrecoverable (input was discarded)
try {
transport.processReadData();
} catch (err) {
if (err instanceof Error && err.message.startsWith('Missing Content-Length')) {
log.error('client speaks unframed stdio — closing session', { err });
await transport.close(); // do NOT reuse the session
return;
}
throw err;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Invalid Content-Length header from MCP client
- Content-Length ${contentLength} exceeds maximum allowed size
- -32000
- Refusing to start the MCP HTTP server on a non-loopback host
- list_repos: "${field}" must be an integer ${bound} (received
AI-assisted analysis of abhigyanpatwari/GitNexus@aac7515d2a (2026-08-20).
Data as JSON: /api/errors/fd95298546ff23fe.
Report an issue: GitHub.