Skip to content

fix(stdio): iterate decoded message list in process_message dispatch#248

Open
TejGandham wants to merge 1 commit intocloudwalk:mainfrom
TejGandham:fix/stdio-transport-list-dispatch
Open

fix(stdio): iterate decoded message list in process_message dispatch#248
TejGandham wants to merge 1 commit intocloudwalk:mainfrom
TejGandham:fix/stdio-transport-list-dispatch

Conversation

@TejGandham
Copy link
Copy Markdown

Summary

  • Message.decode/1 returns {:ok, messages} where messages is always a list (see message.ex:376)
  • The STDIO transport at stdio.ex:243 passed this list directly to process_message/2, which expects a single map
  • This caused a BadMapError on every incoming JSON-RPC message, making the STDIO server transport completely non-functional

Fix

Iterate the decoded list with Enum.each/2 before dispatching each message individually — matching how the client-side code (client/base.ex:971) and other transports (SSE via pattern match {:ok, [message]}) already handle the list return.

Before (broken)

case Message.decode(data) do
  {:ok, messages} ->
    process_message(messages, state)  # messages is a list, process_message expects a map

After (fixed)

case Message.decode(data) do
  {:ok, messages} ->
    Enum.each(messages, &process_message(&1, state))

Verification

Tested against a real MCP client (roundtable) — the STDIO transport now correctly handles initialize, tools/call, and other JSON-RPC messages. All existing tests continue to pass.

🤖 Generated with Claude Code

Message.decode/1 always returns {:ok, messages} where messages is a
list, but the STDIO transport passed this list directly to
process_message/2 which expects a single map. This caused a BadMapError
on every incoming JSON-RPC message, making the STDIO transport
completely non-functional.

The client-side code (client/base.ex) and other transports (SSE,
StreamableHTTP) already handle the list correctly — STDIO was the
only outlier.

Fix: iterate with Enum.each/2 before dispatching each message.
Copy link
Copy Markdown

@cloudwalk-review-agent cloudwalk-review-agent bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

Solid fix. Message.decode/1 returns a list and passing it directly to process_message/2 (which expects a map) was a BadMapError on every message — exactly as described. The Enum.each/2 approach is correct and consistent with how the client side and SSE transport handle the same return shape.

One minor note: Enum.each/2 discards the return value of process_message/2, so any state updates that happen inside won't propagate. If process_message/2 ever needs to thread updated state through multiple messages in a batch, this will silently drop those updates. For a GenServer where state mutations go through handle_info/handle_call callbacks, this is likely fine as-is — but worth being aware of if process_message is ever extended to return {:noreply, new_state} style tuples.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant