CoplayDev/unity-mcp · error · ValueError
Invalid frame length: {length}
Error message
Invalid frame length: {length} What it means
Raised by read_frame() in stress_mcp.py after decoding the 8-byte big-endian length header to a value <=0 or >64MiB. This signals framing desynchronization — the bytes interpreted as a header are not a valid length prefix, meaning the read position is misaligned relative to the bridge's framed protocol.
Source
Thrown at tools/stress_mcp.py:67
pass
return default_port
async def read_exact(reader: asyncio.StreamReader, n: int) -> bytes:
buf = b""
while len(buf) < n:
chunk = await reader.read(n - len(buf))
if not chunk:
raise ConnectionError("Connection closed while reading")
buf += chunk
return buf
async def read_frame(reader: asyncio.StreamReader) -> bytes:
header = await read_exact(reader, 8)
(length,) = struct.unpack(">Q", header)
if length <= 0 or length > (64 * 1024 * 1024):
raise ValueError(f"Invalid frame length: {length}")
return await read_exact(reader, length)
async def write_frame(writer: asyncio.StreamWriter, payload: bytes) -> None:
header = struct.pack(">Q", len(payload))
writer.write(header)
writer.write(payload)
await asyncio.wait_for(writer.drain(), timeout=TIMEOUT)
async def do_handshake(reader: asyncio.StreamReader) -> None:
# Server sends a single line handshake: "WELCOME UNITY-MCP 1 FRAMING=1\n"
line = await reader.readline()
if not line or b"WELCOME UNITY-MCP" not in line:
raise ConnectionError(f"Unexpected handshake from server: {line!r}")
def make_ping_frame() -> bytes:View on GitHub (pinned to c21bf496bc)
Solutions
- Verify the target port is the Unity TCP bridge (discover_port reads unity_port from status files), not the HTTP MCP port.
- After a timeout on read_frame, discard and reconnect rather than reusing the desynchronized reader — the stress client already does this in its except block.
- If the issue persists, inspect raw bytes with MCP_STRESS_DEBUG=1 to see what the bridge is actually sending.
Defensive patterns
Strategy: try-catch
Try / catch
try:
_ = await asyncio.wait_for(read_frame(reader), timeout=TIMEOUT)
except (ValueError, ConnectionError, asyncio.TimeoutError):
# Stream is desynchronized or closed — reconnect from scratch.
if writer:
writer.close()
await writer.wait_closed()
reader, writer = await asyncio.wait_for(asyncio.open_connection(host, port), timeout=TIMEOUT)
await asyncio.wait_for(do_handshake(reader), timeout=TIMEOUT) Prevention
- Never reuse a StreamReader after a framing error — always reconnect.
- Confirm the port is the TCP bridge (unity_port), not the HTTP server.
- After a read timeout, treat the connection as poisoned and reconnect.
When it happens
Trigger: The reader is out of sync with the framed stream: connecting to a non-framed service (e.g. the HTTP MCP endpoint), leftover bytes from an incomplete prior read, or the bridge sent data the client didn't expect (e.g. an unsolicited notification) that shifted the byte boundary.
Common situations: Wrong port — connecting to the Python HTTP server instead of the Unity TCP bridge; a prior read_frame timed out (asyncio.wait_for) leaving partial header bytes in the StreamReader buffer that corrupt the next decode; protocol version mismatch where the bridge uses different framing.
Related errors
- Invalid frame length: {length}
- Unexpected handshake from server: {line!r}
- Unexpected handshake from server: {line!r}
- Connection closed while reading
- Connection closed while reading
AI-assisted analysis of CoplayDev/unity-mcp@c21bf496bc (2026-08-13).
Data as JSON: /api/errors/ece5821b460c68da.
Report an issue: GitHub.