Skip to content

[FEATURE] MCP stdio server exits silently when JSON-RPC messages are CRLF-terminated #668

Description

@jeffreyaven

Summary

stackql mcp --mcp.server.type=stdio exits immediately with code 0 and no error output if an incoming JSON-RPC line is terminated with \r\n instead of \n. The stray \r at the end of the line appears to kill the server's message loop. Nothing is written to stdout, stderr, or the sink log, so from the client's side the server just dies with a clean exit code, which makes this very hard to diagnose.

Environment

  • stackql version: 0.10.500
  • OS: Windows 11 (also reproducible anywhere by sending CRLF line endings)
  • Invocation: stackql mcp --mcp.server.type=stdio --auth='{"github":{"type":"null_auth"}}'

Steps to reproduce

LF-terminated message - works (server responds to initialize):

{ printf '{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"repro","version":"1"}},"id":1}\n'; sleep 8; } \
  | stackql mcp --mcp.server.type=stdio --auth='{"github":{"type":"null_auth"}}'

CRLF-terminated message - server exits with code 0, no response, no error:

{ printf '{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"repro","version":"1"}},"id":1}\r\n'; sleep 8; } \
  | stackql mcp --mcp.server.type=stdio --auth='{"github":{"type":"null_auth"}}'

Equivalent Python repro (this is how it was found - subprocess.Popen(..., text=True) on Windows translates \n to \r\n on the child's stdin):

import json, subprocess, time

cmd = ["stackql", "mcp", "--mcp.server.type=stdio",
       '--auth={"github":{"type":"null_auth"}}']

# text=True on Windows writes \r\n to the child's stdin
proc = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
                        stderr=subprocess.PIPE, text=True)
msg = {"jsonrpc": "2.0", "method": "initialize",
       "params": {"protocolVersion": "2024-11-05", "capabilities": {},
                  "clientInfo": {"name": "repro", "version": "1"}}, "id": 1}
proc.stdin.write(json.dumps(msg) + "\n")
proc.stdin.flush()
time.sleep(10)
print("returncode:", proc.poll())   # 0 - exited, no response ever written

Switching the same script to binary pipes (no newline translation) makes the handshake succeed.

Expected behaviour

Either of:

  1. Tolerate CRLF: strip a trailing \r from each line before JSON parsing. The MCP spec's stdio transport delimits messages with newlines, and CRLF-emitting clients exist in practice (anything spawning the server through a text-mode pipe on Windows).
  2. At minimum, fail loudly: log a parse error to stderr and/or the sink log, and exit non-zero, instead of exiting 0 in silence.

Actual behaviour

The process exits with code 0. Nothing is written to stdout. The sink log file is created but empty. The only stderr output is the usual sink file: <path> line.

Impact

Any MCP client (or test harness) that ends up writing CRLF-terminated lines on Windows sees the server "start and immediately die" with a success exit code and zero diagnostics. This cost real debugging time while smoke testing the Windows .mcpb bundle.

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions