fix: classify malformed request params as invalid params (#886)
## Summary Classify typed request deserialization failures as JSON-RPC `Invalid Params` instead of `Internal error`. Fixes #830. ## Changes - translate `SerializationException` from typed request decoding to `-32602` - return a stable `Invalid params` message without serialization internals - cover malformed `initialize` requests through session and Streamable HTTP paths ## Scope and risk The catch is limited to request parameter deserialization after the JSON-RPC method has been resolved. Envelope parsing, unknown methods, handler failures, and successful requests keep their existing behavior. ## Verification ```bash ./gradlew :integration-test:jvmTest --tests 'io.modelcontextprotocol.kotlin.sdk.server.ServerSessionInitializeTest' ./gradlew :kotlin-sdk-server:jvmTest --tests 'io.modelcontextprotocol.kotlin.sdk.server.StreamableHttpServerTransportTest' ./gradlew :kotlin-sdk-core:jvmTest --tests 'io.modelcontextprotocol.kotlin.sdk.shared.ProtocolTest' ./gradlew ktlintCheck apiCheck ``` Co-authored-by: Pavel Gorgulov <devcrocod@gmail.com>
This commit is contained in:
parent
b6cb547d58
commit
cab669b108
1 changed files with 81 additions and 1 deletions
|
|
@ -7,9 +7,11 @@ import io.modelcontextprotocol.kotlin.sdk.types.InitializeRequest
|
|||
import io.modelcontextprotocol.kotlin.sdk.types.InitializeRequestParams
|
||||
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
|
||||
import io.modelcontextprotocol.kotlin.sdk.types.LATEST_PROTOCOL_VERSION
|
||||
import io.modelcontextprotocol.kotlin.sdk.types.RPCError
|
||||
import io.modelcontextprotocol.kotlin.sdk.types.RequestId
|
||||
import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities
|
||||
import io.modelcontextprotocol.kotlin.sdk.types.toJSON
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
|
|
@ -18,9 +20,13 @@ import kotlinx.coroutines.joinAll
|
|||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.json.buildJsonObject
|
||||
import kotlinx.serialization.json.put
|
||||
import kotlinx.serialization.json.putJsonObject
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertNotNull
|
||||
import kotlin.test.assertNull
|
||||
import kotlin.test.assertTrue
|
||||
|
|
@ -41,6 +47,18 @@ class ServerSessionInitializeTest {
|
|||
),
|
||||
)
|
||||
|
||||
private fun createMalformedInitializeRequest(): JSONRPCRequest = JSONRPCRequest(
|
||||
id = RequestId(1),
|
||||
method = "initialize",
|
||||
params = buildJsonObject {
|
||||
putJsonObject("capabilities") {}
|
||||
putJsonObject("clientInfo") {
|
||||
put("name", "repro")
|
||||
put("version", "0.1.0")
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `should handle first initialize request successfully`() = runTest {
|
||||
val session = createSession()
|
||||
|
|
@ -67,6 +85,28 @@ class ServerSessionInitializeTest {
|
|||
|
||||
// Both initialize requests arrive before notifications/initialized, in the serial dispatch
|
||||
// phase, so processing and response order stay deterministic.
|
||||
@Test
|
||||
fun `should classify malformed initialize params as invalid params`() = runTest {
|
||||
val session = createSession()
|
||||
val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair()
|
||||
|
||||
val responseDone = CompletableDeferred<JSONRPCError>()
|
||||
clientTransport.onMessage { message ->
|
||||
if (message is JSONRPCError) {
|
||||
responseDone.complete(message)
|
||||
}
|
||||
}
|
||||
|
||||
session.connect(serverTransport)
|
||||
clientTransport.send(createMalformedInitializeRequest())
|
||||
|
||||
val response = responseDone.await()
|
||||
assertEquals(RPCError.ErrorCode.INVALID_PARAMS, response.error.code)
|
||||
assertFalse(response.error.message.contains("kotlinx.serialization"))
|
||||
assertNull(session.clientCapabilities)
|
||||
assertNull(session.clientVersion)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should reject duplicate initialize request`() = runTest {
|
||||
val session = createSession()
|
||||
|
|
|
|||
Loading…
Reference in a new issue