Skip to content

Strip stdio client frames as bytes - #627

Open
koic wants to merge 1 commit into
modelcontextprotocol:mainfrom
koic:strip_stdio_client_frames_as_bytes
Open

koic wants to merge 1 commit into
modelcontextprotocol:mainfrom
koic:strip_stdio_client_frames_as_bytes

Conversation

@koic

@koic koic commented Oct 21, 2026

Copy link
Copy Markdown
Member

Motivation and Context

MCP::Client::Stdio reads the server's stdout as UTF-8 and strips each frame before parsing it. String#strip raises for a UTF-8 string holding an invalid byte at either end on every supported Ruby, and anywhere in it from Ruby 3.2 on, and the read loop rescued only JSON::ParserError. A server writing such a frame therefore failed the awaited request with ArgumentError or Encoding::CompatibilityError instead of the transport's own RequestHandlerError, which callers rescue. The server transport strips its frames the same way and has the same change separately.

Frames are now stripped as bytes. A frame that is not JSON fails the request with the transport's parse failure, as any other unparsable frame does, and a response holding such a byte in a value is returned with the byte kept.

How Has This Been Tested?

test/mcp/client/stdio_test.rb: a frame starting with an invalid byte and a response holding one in a value. Against the previous library the first raises ArgumentError and the second Encoding::CompatibilityError.

Breaking Changes

None.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

## Motivation and Context

`MCP::Client::Stdio` reads the server's stdout as UTF-8 and strips each frame before parsing it. `String#strip`
raises for a UTF-8 string holding an invalid byte at either end on every supported Ruby, and anywhere in it from
Ruby 3.2 on, and the read loop rescued only `JSON::ParserError`. A server writing such a frame therefore failed
the awaited request with `ArgumentError` or `Encoding::CompatibilityError` instead of the transport's own
`RequestHandlerError`, which callers rescue. The server transport strips its frames the same way and has the same
change separately.

Frames are now stripped as bytes. A frame that is not JSON fails the request with the transport's parse failure,
as any other unparsable frame does, and a response holding such a byte in a value is returned with the byte kept.

## How Has This Been Tested?

`test/mcp/client/stdio_test.rb`: a frame starting with an invalid byte and a response holding one in a value.
Against the previous library the first raises `ArgumentError` and the second `Encoding::CompatibilityError`.

## Breaking Changes

None.

This branch has not been deployed

No deployments
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