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

  1. Verify the target port is the Unity TCP bridge (discover_port reads unity_port from status files), not the HTTP MCP port.
  2. 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.
  3. 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

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


AI-assisted analysis of CoplayDev/unity-mcp@c21bf496bc (2026-08-13). Data as JSON: /api/errors/ece5821b460c68da. Report an issue: GitHub.