[WIP] StreamableHttpServerTransport implementation (#449)

# [WIP] StreamableHttpServerTransport implementation

- Implement StreamableHttpServerTransport for MCP spec
- Support both SSE streaming and direct HTTP JSON responses
- Add event store interface for resumability

> [!IMPORTANT]  
> The work is in progress. StreamableHttpServerTransport is not
production-ready yet!!!

Based on #235 

## How Has This Been Tested?
<!-- Have you tested this in a real application? Which scenarios were
tested? -->

## Breaking Changes
None

## 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
This commit is contained in:
Pavel Gorgulov 2025-12-05 18:51:19 +01:00 • committed by GitHub
commit 10a0b67204
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 1024 additions and 3 deletions

View file

@ -4,6 +4,7 @@ import io.github.oshai.kotlinlogging.KotlinLogging
import io.modelcontextprotocol.kotlin.sdk.types.CancelledNotification
import io.modelcontextprotocol.kotlin.sdk.types.CancelledNotificationParams
import io.modelcontextprotocol.kotlin.sdk.types.EmptyResult
import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCEmptyMessage
import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCError
import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCNotification
import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCRequest
@ -249,6 +250,7 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio
is JSONRPCRequest -> onRequest(message)
is JSONRPCNotification -> onNotification(message)
is JSONRPCError -> onResponse(null, message)
is JSONRPCEmptyMessage -> Unit
}
}

View file

@ -1,7 +1,5 @@
package io.modelcontextprotocol.kotlin.sdk.types
import io.modelcontextprotocol.kotlin.sdk.types.Icon.Theme.Dark
import io.modelcontextprotocol.kotlin.sdk.types.Icon.Theme.Light
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonObject
@ -12,6 +10,8 @@ import kotlinx.serialization.json.JsonObject
public const val LATEST_PROTOCOL_VERSION: String = "2025-06-18"
public const val DEFAULT_NEGOTIATED_PROTOCOL_VERSION: String = "2025-03-26"
public val SUPPORTED_PROTOCOL_VERSIONS: List<String> = listOf(
LATEST_PROTOCOL_VERSION,
"2025-03-26",

View file

@ -85,6 +85,11 @@ public sealed interface JSONRPCMessage {
public val jsonrpc: String
}
@Serializable
public data object JSONRPCEmptyMessage : JSONRPCMessage {
override val jsonrpc: String = JSONRPC_VERSION
}
// ============================================================================
// JSONRPCRequest
// ============================================================================
@ -197,7 +202,7 @@ public data class JSONRPCResponse(val id: RequestId, val result: RequestResult =
* @property error Details about the error that occurred, including error code and message.
*/
@Serializable
public data class JSONRPCError(val id: RequestId, val error: RPCError) : JSONRPCMessage {
public data class JSONRPCError(val id: RequestId?, val error: RPCError) : JSONRPCMessage {
@EncodeDefault
override val jsonrpc: String = JSONRPC_VERSION
}

View file

@ -384,6 +384,7 @@ internal object JSONRPCMessagePolymorphicSerializer :
"result" in jsonObject -> JSONRPCResponse.serializer()
"method" in jsonObject && "id" in jsonObject -> JSONRPCRequest.serializer()
"method" in jsonObject -> JSONRPCNotification.serializer()
jsonObject.isEmpty() || jsonObject.keys == setOf("jsonrpc") -> JSONRPCEmptyMessage.serializer()
else -> throw SerializationException("Invalid JSONRPCMessage type: ${jsonObject.keys}")
}
}

View file

@ -430,4 +430,16 @@ class JsonRpcTest {
request.method shouldBe "notifications/log"
request.params shouldBeSameInstanceAs params
}
@Test
fun `should deserialize JSONRPCEmptyMessage`() {
val json = """
{
"jsonrpc": "2.0"
}
""".trimIndent()
val message = McpJson.decodeFromString<JSONRPCMessage>(json)
message shouldBeSameInstanceAs JSONRPCEmptyMessage
}
}