The problem
In the simplest MCP (Model Context Protocol) transport, a host starts a server as a subprocess, and the two exchange messages over its standard input and output. Each message is one JSON (JavaScript Object Notation) object on one line: messages are delimited by newlines and must not contain embedded newlines.
Writing is easy. Reading is the tricky part: one read from a pipe can return half a message, or three messages and the start of a fourth. So the reader must buffer: keep what it has not yet been able to use.
Writing
encode(message) returns the message as one line: compact JSON (json.dumps(message, separators=(",", ":"))) followed by "\n". JSON writes a newline inside a text as the two characters \n, so the line never contains a raw newline.
Example.
encode({"a": "line1\nline2", "id": 1})is{"a":"line1\nline2","id":1}followed by a real newline: inside the text, the break is the two characters backslash andn.
Reading
LineReader keeps an internal buffer. feed(chunk) takes a string just read from the stream, appends it to the buffer, and returns the list of complete messages now available, in order, parsed with json.loads. An incomplete last line stays in the buffer for the next call. A complete line that is not valid JSON gives {"parse_error": <the line>} in the list instead of a message. Empty lines are skipped.
Example. Three reads in a row (
\nstands for a newline).
- The first read delivers
{"id":1}, a newline and{"id": a message and half of the next. The reader returns the first message and keeps{"id"in its buffer.- The second read delivers
:2}, a newline,{"id":3}, a newline,not jsonand a newline. The buffer plus the new text holds three complete lines: the reader returns the messages with id 2 and 3, and{"parse_error": "not json"}for the third line.- The third read delivers
{"id":4}with no newline: nothing is returned, and the text stays in the buffer, because the message may still grow.
What to implement
encode(message): the line above.class LineReaderwithfeed(self, chunk): the reader above.
Requirements
- Standard library only.
Hint
text.split("\n") returns the pieces between newlines; the last piece is whatever comes after the last newline: it is either empty or an incomplete line. That last piece is the new buffer.