Skip to content

mcp_server: stdio framing mismatch — claude mcp list shows red (Content-Length vs NDJSON) #192

@Gradata

Description

@Gradata

Problem

claude mcp list shows the gradata MCP entry as red (✗ Failed to connect) even when the server is healthy and tools work end-to-end inside a real MCP session.

Root cause: stdio framing protocol mismatch.

  • gradata.mcp_server reads/writes LSP-style framing: Content-Length: N\r\n\r\n{json}
  • Claude Code's mcp list health probe sends NDJSON: {json}\n per message

The gradata server waits for a Content-Length: header that never arrives, so the probe times out and the entry shows red. Inside an actual MCP session (where the client knows to use Content-Length), everything works — the 55-tool catalogue loads correctly, tools/call works, no errors.

Evidence

  • Manual Content-Length-framed initialize handshake against python3 -m gradata.mcp_server returns the correct server-info response (verified during PR feat: daemon speaks both HTTP and MCP transports #191 work).
  • Sending NDJSON to the same process gets no response.
  • claude mcp list consistently reports ✗ Failed to connect for the gradata entry. Other MCP servers in the same ~/.claude.json show green.
  • This predates PR feat: daemon speaks both HTTP and MCP transports #191 — daemon-speaks-both-transports does not change the stdio framing path, only the bridge to daemon HTTP.

Options

Option Pros Cons
A. Auto-detect framing on first byte ({ → NDJSON, C → Content-Length) Backward compatible; supports both clients Slight complexity in the read loop
B. Switch to NDJSON entirely Simpler; matches what Cursor + Claude Code use today Breaks any existing client that already sends Content-Length
C. Document the red status as cosmetic Zero code change User-hostile; every health probe will be a false alarm

Recommend A — auto-detect. Implementation sketch: peek first byte of stdin; if { (NDJSON) or [ (NDJSON batch), use line-delimited reader; else parse LSP Content-Length: headers as today.

Acceptance criteria

  • claude mcp list | grep gradata shows green / connected
  • Existing Content-Length-speaking clients continue to work
  • No regression in PR feat: daemon speaks both HTTP and MCP transports #191 daemon bridge mode (which doesn't use stdio framing at all — HTTP only)
  • Unit test for both framings

Related

Follow-up from #191. Not a regression — pre-existing protocol issue exposed during verification.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingmcpMCP server / client integration

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions