diff --git a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt index 1f8ac39..3b02835 100644 --- a/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt +++ b/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransport.kt @@ -459,9 +459,10 @@ public class StreamableHttpServerTransport(private val configuration: Configurat } val messages = parseBody(call) ?: return - val isInitializationRequest = messages.any { - it is JSONRPCRequest && it.method == Method.Defined.Initialize.value + val initializationRequest = messages.filterIsInstance().firstOrNull { + it.method == Method.Defined.Initialize.value } + val isInitializationRequest = initializationRequest != null if (isInitializationRequest) { if (initialized.load() && sessionId != null) { @@ -469,6 +470,7 @@ public class StreamableHttpServerTransport(private val configuration: Configurat HttpStatusCode.BadRequest, RPCError.ErrorCode.INVALID_REQUEST, "Invalid Request: Server already initialized", + initializationRequest.id, ) return } @@ -506,8 +508,7 @@ public class StreamableHttpServerTransport(private val configuration: Configurat // For initialize requests, get from request params. // For other requests, get from header (already validated). val clientProtocolVersion = if (isInitializationRequest) { - val initRequest = messages.first() as JSONRPCRequest - (initRequest.params as? JsonObject)?.get("protocolVersion") + (initializationRequest.params as? JsonObject)?.get("protocolVersion") ?.let { McpJson.decodeFromJsonElement(it) } ?: DEFAULT_NEGOTIATED_PROTOCOL_VERSION } else { @@ -913,7 +914,12 @@ public class StreamableHttpServerTransport(private val configuration: Configurat } } -internal suspend fun ApplicationCall.reject(status: HttpStatusCode, code: Int, message: String) { +internal suspend fun ApplicationCall.reject( + status: HttpStatusCode, + code: Int, + message: String, + id: RequestId? = null, +) { this.response.status(status) - this.respond(JSONRPCError(id = null, error = RPCError(code = code, message = message))) + this.respond(JSONRPCError(id = id, error = RPCError(code = code, message = message))) } diff --git a/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransportTest.kt b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransportTest.kt index 172889a..0d99f51 100644 --- a/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransportTest.kt +++ b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransportTest.kt @@ -38,6 +38,7 @@ import io.modelcontextprotocol.kotlin.sdk.types.Implementation import io.modelcontextprotocol.kotlin.sdk.types.InitializeRequest import io.modelcontextprotocol.kotlin.sdk.types.InitializeRequestParams import io.modelcontextprotocol.kotlin.sdk.types.InitializedNotification +import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCError import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCRequest import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCResponse @@ -163,7 +164,7 @@ class StreamableHttpServerTransportTest { } @Test - fun `second initialization request returns an HTTP error`() = testApplication { + fun `second initialization request returns JSON-RPC error with request id`() = testApplication { configTestServer() val client = createTestClient() @@ -186,13 +187,17 @@ class StreamableHttpServerTransportTest { firstResponse.status shouldBe HttpStatusCode.OK + val secondRequest = buildInitializeRequestPayload().copy(id = RequestId("second-init")) val secondResponse = client.post(path) { addStreamableHeaders() header("mcp-session-id", firstResponse.headers[MCP_SESSION_ID_HEADER]) - setBody(payload) + setBody(secondRequest) } secondResponse.status shouldBe HttpStatusCode.BadRequest + val error = secondResponse.body() + error.id shouldBe secondRequest.id + error.error.message shouldBe "Invalid Request: Server already initialized" } @Test