Handle additional error types in client initialization handling (#404)

May throw the following exceptions: `CancellationException`,
`McpException`, `StreamableHttpError`, and `SerializationException`

## Motivation and Context
This makes it possible to handle errors directly without needing to
inspect the cause. For example:
```kotlin
try {
    client.connect(transport)
} catch (e: StreamableHttpError) {
    // ...
}
```

## How Has This Been Tested?
locally

## Breaking Changes
None

## Types of changes
- [x] 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
- [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
- [ ] I have added or updated documentation as needed
This commit is contained in:
Pavel Gorgulov 2025-11-20 19:09:23 +01:00 • committed by GitHub
commit 0f9209ad4a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -26,6 +26,7 @@ import io.modelcontextprotocol.kotlin.sdk.types.ListToolsResult
import io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel
import io.modelcontextprotocol.kotlin.sdk.types.LoggingMessageNotification
import io.modelcontextprotocol.kotlin.sdk.types.LoggingMessageNotificationParams
import io.modelcontextprotocol.kotlin.sdk.types.McpException
import io.modelcontextprotocol.kotlin.sdk.types.Method
import io.modelcontextprotocol.kotlin.sdk.types.Role
import io.modelcontextprotocol.kotlin.sdk.types.Root
@ -232,6 +233,82 @@ class ClientTest {
assertTrue(closed)
}
@Test
fun `should rethrow McpException as is`() = runTest {
var closed = false
val failingTransport = object : AbstractTransport() {
override suspend fun start() {}
override suspend fun send(message: JSONRPCMessage) {
if (message !is JSONRPCRequest) return
check(message.method == Method.Defined.Initialize.value)
throw McpException(
code = -32600,
message = "Invalid Request",
)
}
override suspend fun close() {
closed = true
}
}
val client = Client(
clientInfo = Implementation(
name = "test client",
version = "1.0",
),
options = ClientOptions(),
)
val exception = assertFailsWith<McpException> {
client.connect(failingTransport)
}
assertEquals(-32600, exception.code)
assertEquals("MCP error -32600: Invalid Request", exception.message)
assertTrue(closed)
}
@Test
fun `should rethrow StreamableHttpError as is`() = runTest {
var closed = false
val failingTransport = object : AbstractTransport() {
override suspend fun start() {}
override suspend fun send(message: JSONRPCMessage) {
if (message !is JSONRPCRequest) return
check(message.method == Method.Defined.Initialize.value)
throw StreamableHttpError(
code = 500,
message = "Internal Server Error",
)
}
override suspend fun close() {
closed = true
}
}
val client = Client(
clientInfo = Implementation(
name = "test client",
version = "1.0",
),
options = ClientOptions(),
)
val exception = assertFailsWith<StreamableHttpError> {
client.connect(failingTransport)
}
assertEquals(500, exception.code)
assertEquals("Streamable HTTP error: Internal Server Error", exception.message)
assertTrue(closed)
}
@Test
fun `should respect server capabilities`() = runTest {
val serverOptions = ServerOptions(
@ -922,7 +999,7 @@ class ClientTest {
println("Client connected")
},
launch {
serverSessionResult.complete(server.connect(serverTransport))
serverSessionResult.complete(server.createSession(serverTransport))
println("Server connected")
},
).joinAll()