Framing and caps
This page covers how MSP bytes reach the host and how they are cut into messages: the transports, the newline framing, and the frame size cap.
Transports: what you actually build against
Section titled “Transports: what you actually build against”Stdio is the whole of the v1 wire. Your client spawns muse serve, owns
its stdin and stdout, and is the single connection for that process’s
lifetime. Two more out-of-process transports — a unix socket and a websocket
— are designed but deferred past v1; no v1 implementation may assume them.
There is also an in-process transport: the first-party terminal UI hosts the session server inside its own process and calls the model directly — no frames, no bytes. It is not something an external client can use, and nothing on this page about framing applies to it by construction. What matters to you is the guarantee that pairing creates: both transports must produce identical item streams — same items, kinds, identities, terminal states, and cursors — so the wire you implement against is never a degraded view.
Because only the spawning party can reach the host’s stdin, v1 has no
authentication mechanism, and that is a normative rule: a host must not
accept a connection from a party that did not spawn it. The
clientInfo you send in the
handshake is diagnostics and attribution, never authorization.
NDJSON framing
Section titled “NDJSON framing”On stdio, framing is NDJSON — newline-delimited JSON:
- One complete JSON-RPC message per line: a UTF-8 encoded JSON object followed by a single line-feed byte.
- A carriage return before the line feed is tolerated on input and never
emitted by the server. The corpus pins this with the
framing-tolerance-cr-inputscenario. - A message must not contain unescaped newline bytes; standard JSON string escaping already guarantees this — never pretty-print a frame onto the wire.
- Empty lines are ignored on input.
- Every message is a JSON-RPC 2.0 object and carries the literal JSON-RPC version member — MSP deliberately keeps it so off-the-shelf JSON-RPC tooling interoperates.
Order is significant: within one connection the server writes frames in a total order, and per-session view events on that connection are cursor-ordered. Read frames in the order they arrive; do not reorder.
The frame cap
Section titled “The frame cap”The server enforces a maximum frame size, 10 MiB by default. The cap is directional in both directions:
- Inbound (your frames): an oversized frame is rejected with the typed
inputTooLargeerror if the server could recover anidfrom the frame prefix, or with a parse error whoseidis null otherwise. The connection survives — the transport is not torn down for a single oversized frame. - Outbound (server frames): the server sizes its own frames under the same configured limit, so a client never receives a frame larger than the one it is expected to accept. You may size your read buffers to the limit.
Every transcript in the conformance corpus records the limit it was
generated under as serverConfig.frameLimitBytes in its manifest.json,
and replay always starts the host with that recorded configuration — that is
how the limit-dependent fixture families reproduce deterministically instead
of diverging at the default limit.
What your client must do at the cap
Section titled “What your client must do at the cap”- Do not retry an oversized frame as-is:
inputTooLargeis not retryable — the fix is to shrink the input, and the error’sdatacarrieslimitBytestelling you the budget. - Split, don’t grow: large payloads travel through methods designed to page — for example truncated item output is fetched with byte-ranged reads rather than one giant frame. If a frame you want to send approaches the limit, restructure the call; there is no frame continuation mechanism.
- Expect budget-shaped responses: when a result cannot fit the frame
budget, the server degrades in specified ways — for example serving a
smaller history mode on
session/resumethan the one requested — rather than emitting an oversized frame. Your client reads what was actually served instead of assuming what it asked for.
Handshake before anything else
Section titled “Handshake before anything else”Each connection performs its own handshake, and framing alone gets you
nothing until it completes: until
initialize succeeds, every
other request is rejected as not-initialized, and the server sends the
connection no notifications. The order is fixed: the client sends
initialize, the server replies, the client sends the
initialized
notification, and only then does the server begin delivering notifications
and server-initiated requests. Capability and opt-out settings are fixed for
the connection lifetime; to change them, reconnect.