kotlin-sdk/integration-test
Repository files (latest commit first)
Filename Latest commit message Latest commit date
UnscientificJsZhai a06d699d6c
feat: SSE heartbeat (#761)
## Summary

Adds optional SSE heartbeat configuration for streamable HTTP MCP server
connections.

This lets `Application.mcpStreamableHttp` callers pass Ktor's
`Heartbeat` configuration for SSE streams, including heartbeat period
and event payload. Heartbeats remain disabled by default, preserving the
existing stream behavior unless the new option is explicitly provided.

## Motivation and Context

Some MCP clients need heartbeat messages on SSE connections to keep the
connection alive. Without those heartbeats, the client may proactively
disconnect from the MCP server. MCP server developers need a way to
configure an SSE heartbeat mechanism so they can avoid unexpected client
disconnects.

In my case, my MCP server was consistently disconnected by Gemini CLI. I
worked around the issue with some fallback approaches, but I believe the
best implementation is to add heartbeat support by using Ktor's built-in
API.

## Implementation Notes

- Added a function overload of `Application.mcpStreamableHttp` to
introduce heartbeat parameter `sseHeartbeatConfig: (Heartbeat.() ->
Unit)?`.
- Applied the configuration inside the Ktor `sse { ... }` block before
handling the existing streamable HTTP transport request.
- Updated the server API dump for the new public API surface.

## How Has This Been Tested?

Added `StreamableHttpHeartbeatIntegrationTest` covering:

- Configured GET SSE streams apply the provided heartbeat configuration.
- GET SSE streams do not send default heartbeats when
`sseHeartbeatConfig` is omitted.
- Configured GET SSE streams emit heartbeat events repeatedly.
- Legacy MCP over SSE APIs do not expose the new heartbeat
configuration.

Verified locally with:

```bash
./gradlew :integration-test:jvmTest --tests "io.modelcontextprotocol.kotlin.sdk.integration.streamablehttp.StreamableHttpHeartbeatIntegrationTest"
```

Result: `BUILD SUCCESSFUL`.

## Breaking Changes

None.

This change adds a new `Application.mcpStreamableHttp` overload that
accepts
`sseHeartbeatConfig: (Heartbeat.() -> Unit)?` while keeping the existing
overload
unchanged. Existing callers continue to compile and keep the previous
behavior:
SSE heartbeats are not sent unless the new overload is used with an
explicit
heartbeat configuration.

## Types of changes

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

## Checklist

- [x] I have read the [MCP
Documentation](https://modelcontextprotocol.io)
- [x] My code follows the repository's style guidelines
- [x] New and existing tests pass locally
- [x] I have added appropriate error handling
- [x] I have added or updated documentation as needed

## Additional context

Heartbeat behavior is delegated to Ktor's SSE heartbeat support, so
applications can use the same configuration semantics they would use in
native Ktor SSE routes.

Related issue: [SSE needs a heartbeat #344
](https://github.com/modelcontextprotocol/kotlin-sdk/issues/344)

---------

Co-authored-by: Pavel Gorgulov <devcrocod@gmail.com>
2026-07-23 14:37:37 +02:00
..
src feat: SSE heartbeat (#761) 2026-07-23 14:37:37 +02:00
build.gradle.kts refactor: explicit locked detekt config (#729) 2026-04-29 12:31:49 +02:00