Description
When an MCP client writes a UTF-8 BOM (\uFEFF / bytes EF BB BF) at the beginning of stdin before the first JSON-RPC message, StdioServerTransport fails on deserialization and immediately closes the session.
We observed this behavior when running against OpenAI Codex CLI / Codex on Windows (codex-cli 0.147.0).
Stack trace
java.lang.IllegalArgumentException: Element class kotlinx.serialization.json.JsonLiteral is not a JsonObject
at kotlinx.serialization.json.JsonElementKt.getJsonObject(JsonElement.kt:232)
at io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessagePolymorphicSerializer.selectDeserializer(serializers.kt:493)
at io.modelcontextprotocol.kotlin.sdk.shared.ReadBufferKt.deserializeMessage(ReadBuffer.kt:136)
at io.modelcontextprotocol.kotlin.sdk.shared.ReadBuffer.readMessage(ReadBuffer.kt:54)
at io.modelcontextprotocol.kotlin.sdk.server.StdioServerTransport.processorPump(StdioServerTransport.kt:209)
Root cause
StdioServerTransport reads bytes into ReadBuffer, splits on newlines, and passes the raw line string to deserializeMessage(line). When the first line contains a leading BOM, kotlinx.serialization parses \uFEFF{...} as a JsonLiteral rather than a JsonObject. JSONRPCMessagePolymorphicSerializer.selectDeserializer expects a JsonObject and throws IllegalArgumentException. Because this exception happens inside processorPump, the coroutine fails and drops the stdio connection.
Suggested fix
- In
ReadBuffer (or deserializeMessage): trim leading BOM characters on incoming lines (line.trimStart('\uFEFF')).
- Alternatively, in
StdioServerTransport: strip the leading EF BB BF bytes from the input Source before appending to the read buffer.
Description
When an MCP client writes a UTF-8 BOM (
\uFEFF/ bytesEF BB BF) at the beginning ofstdinbefore the first JSON-RPC message,StdioServerTransportfails on deserialization and immediately closes the session.We observed this behavior when running against OpenAI Codex CLI / Codex on Windows (
codex-cli 0.147.0).Stack trace
Root cause
StdioServerTransportreads bytes intoReadBuffer, splits on newlines, and passes the raw line string todeserializeMessage(line). When the first line contains a leading BOM,kotlinx.serializationparses\uFEFF{...}as aJsonLiteralrather than aJsonObject.JSONRPCMessagePolymorphicSerializer.selectDeserializerexpects aJsonObjectand throwsIllegalArgumentException. Because this exception happens insideprocessorPump, the coroutine fails and drops the stdio connection.Suggested fix
ReadBuffer(ordeserializeMessage): trim leading BOM characters on incoming lines (line.trimStart('\uFEFF')).StdioServerTransport: strip the leadingEF BB BFbytes from the inputSourcebefore appending to the read buffer.