Add specific error handling for client connect (#64)

* feat: add specific error handling for client connect

* chore: fix typo

* feat(tests): Add unit test for client connect function

* feat: remove else block to improve readability

* feat: remove unneeded file

* feat: revert change to misc.xml

---------

Co-authored-by: Leonid Stashevsky <e5l@users.noreply.github.com>
This commit is contained in:
SeanChinJunKai 2025-04-10 22:23:35 +08:00 • committed by GitHub
commit 63288ea63a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 67 additions and 4 deletions

View file

@ -10,6 +10,7 @@ import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonNull
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import kotlin.coroutines.cancellation.CancellationException
/**
* Options for configuring the MCP client.
@ -100,7 +101,12 @@ public open class Client(
notification(InitializedNotification())
} catch (error: Throwable) {
close()
if (error !is CancellationException) {
throw IllegalStateException("Error connecting to transport: ${error.message}")
}
throw error
}
}

View file

@ -6,6 +6,8 @@ import io.modelcontextprotocol.kotlin.sdk.CreateMessageResult
import io.modelcontextprotocol.kotlin.sdk.EmptyJsonObject
import io.modelcontextprotocol.kotlin.sdk.Implementation
import InMemoryTransport
import io.mockk.coEvery
import io.mockk.spyk
import io.modelcontextprotocol.kotlin.sdk.InitializeRequest
import io.modelcontextprotocol.kotlin.sdk.InitializeResult
import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage
@ -49,13 +51,13 @@ import kotlin.test.fail
class ClientTest {
@Test
fun `should initialize with matching protocol version`() = runTest {
var initialied = false
var initialised = false
val clientTransport = object : AbstractTransport() {
override suspend fun start() {}
override suspend fun send(message: JSONRPCMessage) {
if (message !is JSONRPCRequest) return
initialied = true
initialised = true
val result = InitializeResult(
protocolVersion = LATEST_PROTOCOL_VERSION,
capabilities = ServerCapabilities(),
@ -90,7 +92,7 @@ class ClientTest {
)
client.connect(clientTransport)
assertTrue(initialied)
assertTrue(initialised)
}
@Test
@ -189,6 +191,61 @@ class ClientTest {
assertTrue(closed)
}
@Test
fun `should reject due to non cancellation exception`() = runTest {
var closed = false
val clientTransport = object : AbstractTransport() {
override suspend fun start() {}
override suspend fun send(message: JSONRPCMessage) {
if (message !is JSONRPCRequest) return
check(message.method == Method.Defined.Initialize.value)
val result = InitializeResult(
protocolVersion = LATEST_PROTOCOL_VERSION,
capabilities = ServerCapabilities(),
serverInfo = Implementation(
name = "test",
version = "1.0"
)
)
val response = JSONRPCResponse(
id = message.id,
result = result
)
_onMessage.invoke(response)
}
override suspend fun close() {
closed = true
}
}
val mockClient = spyk(
Client(
clientInfo = Implementation(
name = "test client",
version = "1.0"
),
options = ClientOptions()
)
)
coEvery{
mockClient.request<InitializeResult>(any())
} throws IllegalStateException("Test error")
val exception = assertFailsWith<IllegalStateException> {
mockClient.connect(clientTransport)
}
assertEquals("Error connecting to transport: Test error", exception.message)
assertTrue(closed)
}
@Test
fun `should respect server capabilities`() = runTest {
val serverOptions = ServerOptions(