Skip to content

Conversation

@kpavlov
Copy link
Contributor

@kpavlov kpavlov commented Nov 27, 2025

Refactor StreamableHttpClientTransport to skip SSE when the data field is empty.

Motivation and Context

TypeScript SSE Server started returning Server-Sent Events with only id and empty data field (Heartbeat/Checkpoint), followed by an event with data, e.g.:

id: d85c6bb0-0fa2-4828-81ad-3e951131aea5_1764226144689_7qb3iojc
data: 

event: message
id: d85c6bb0-0fa2-4828-81ad-3e951131aea5_1764226144690_sodh20lo
data: {"result":{"protocolVersion":"2025-06-18","capabilities":{"logging":{},"tools":{"listChanged":true},"prompts":{"listChanged":true},"completions":{},"resources":{"listChanged":true}},"serverInfo":{"name":"simple-streamable-http-server","version":"1.0.0"}},"jsonrpc":"2.0","id":"7ce065b0678f49e5b04ce5a0fcc7d518"}

Such empty events might be used by the server as Heartbeat or Checkpoint events. The client should retain the updated Last-Event-ID and disregard the payload. It is essential to handle such responses to ensure compatibility with updated TypeScript SDK.

How Has This Been Tested?

  • Added integration test
  • Regression tests are green now

Breaking Changes

No

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

Additional context

Refactor StreamableHttpClientTransport to skip SSE with an empty data field.
@kpavlov kpavlov added the bugfix Something was fixed 🎉 label Nov 27, 2025
- Added a new test case to verify handling of empty Server-Sent Events (SSE).
- Replace `OldSchemaMockMcp` with `MockMcp`
Comment on lines +349 to +352
// reset
id = null
eventName = null
sb.clear()
Copy link
Contributor Author

Choose a reason for hiding this comment

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

less side-effects inside dispath

Comment on lines 52 to 59
"id: ${Uuid.random()}\n",
"data: \n",
"\n",
"id: ${Uuid.random()}\n",
"event: message\n",
@Suppress("MaxLineLength")
"data: {\"result\":{\"protocolVersion\":\"2025-06-18\",\"capabilities\":{},\"serverInfo\":{\"name\":\"simple-streamable-http-server\",\"version\":\"1.0.0\"}},\"jsonrpc\":\"2.0\",\"id\":\"7ce065b0678f49e5b04ce5a0fcc7d518\"}\n",
"\n",
Copy link
Contributor Author

Choose a reason for hiding this comment

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

raw SSE stream data

@kpavlov kpavlov marked this pull request as ready for review November 27, 2025 08:04
@kpavlov kpavlov changed the title fix(sse-client): Skip empty events in StreamableHttpClientTransport fix(sse-client): Skip SSE in StreamableHttpClientTransport when the data field is empty Nov 27, 2025
@kpavlov kpavlov changed the title fix(sse-client): Skip SSE in StreamableHttpClientTransport when the data field is empty fix(sse-client): Skip SSE in StreamableHttpClientTransport when data is empty Nov 27, 2025
lastEventId = it
onResumptionToken?.invoke(it)
}
if (data.isBlank()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Should it be isEmpty() instead?

Copy link
Contributor

Choose a reason for hiding this comment

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

Agree
isBlank will also skip a string if it contains only whitespace. But such a value is valid

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There’s no need to worry about spaces when parsing, since it’s not JSON.

internal class StreamableHttpClientTest : AbstractStreamableHttpClientTest() {

@Test
fun `Should skip empty SSE`(): Unit = runBlocking {
Copy link
Contributor

Choose a reason for hiding this comment

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

plus the following cases?

  • should handle whitespace-only SSE data fields
  • should handle multi-line SSE data fields
flowOf(
        "event: message\n",
        "data: {\"result\":{\"protocolVersion\":\"2025-06-18\",\n",
        "data: \"capabilities\":{},\n",
        "data: \"serverInfo\":{\"name\":\"simple-streamable-http-server\",\"version\":\"1.0.0\"}},\n",
        "data: \"jsonrpc\":\"2.0\",\"id\":\"7ce065b0678f49e5b04ce5a0fcc7d518\"}\n",
        "\n",
)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point. I will update the integration test for multiline

lastEventId = it
onResumptionToken?.invoke(it)
}
if (data.isBlank()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Agree
isBlank will also skip a string if it contains only whitespace. But such a value is valid

val line = channel.readUTF8Line() ?: break
if (line.isEmpty()) {
dispatch(sb.toString())
dispatch(id = id, eventName = eventName, data = sb.toString())
Copy link
Contributor

Choose a reason for hiding this comment

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

In that case, you can move dispatch fun out of handleInlineSse

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not quite yet. The "lastEventId" is updated inside. I wanted to avoid a major refactoring for now.

… scenarios

- Refactored SSE test cases to include handling of empty data, tabs, spaces, and multiline data payloads.
@kpavlov kpavlov requested a review from devcrocod November 27, 2025 14:29
@kpavlov kpavlov merged commit e584cfe into main Nov 27, 2025
4 checks passed
@kpavlov kpavlov deleted the kpavlov/fix-empty-data branch November 27, 2025 16:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugfix Something was fixed 🎉

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants