feat: Add kotlin-sdk-testing module with channel-based transport implementation (#505)

# feat: Add kotlin-sdk-testing module with channel-based transport
implementation

- Introduced `kotlin-sdk-testing` module for testing utilities in the
MCP Kotlin SDK.
- Implemented `ChannelTransport`, providing JSON-RPC transport via
Kotlin coroutines channels.

<!-- Provide a brief summary of your changes -->

## Motivation and Context
This ChannelTransport can be used for testing Server implementation
without using real transports.

## How Has This Been Tested?
Unit test

## Breaking Changes
No

## 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)
- [x] Documentation update

## Checklist
<!-- Go over all the following points, and put an `x` in all the boxes
that apply. -->
- [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:
Konstantin Pavlov 2026-02-16 14:10:56 +02:00 • committed by GitHub
commit 212cbfa325
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 669 additions and 3 deletions

View file

@ -30,6 +30,7 @@ kotlin {
jvmTest {
dependencies {
implementation(project(":test-utils"))
implementation(project(":kotlin-sdk-testing"))
}
}
}

View file

@ -0,0 +1,88 @@
package io.modelcontextprotocol.kotlin.sdk.testing
import io.kotest.matchers.collections.shouldHaveSize
import io.kotest.matchers.nulls.shouldNotBeNull
import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi
import io.modelcontextprotocol.kotlin.sdk.client.Client
import io.modelcontextprotocol.kotlin.sdk.server.Server
import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions
import io.modelcontextprotocol.kotlin.sdk.server.ServerSession
import io.modelcontextprotocol.kotlin.sdk.types.Implementation
import io.modelcontextprotocol.kotlin.sdk.types.InitializeRequest
import io.modelcontextprotocol.kotlin.sdk.types.InitializeResult
import io.modelcontextprotocol.kotlin.sdk.types.LATEST_PROTOCOL_VERSION
import io.modelcontextprotocol.kotlin.sdk.types.ListResourcesRequest
import io.modelcontextprotocol.kotlin.sdk.types.ListResourcesResult
import io.modelcontextprotocol.kotlin.sdk.types.Method
import io.modelcontextprotocol.kotlin.sdk.types.Resource
import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.joinAll
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlin.test.Test
@OptIn(ExperimentalMcpApi::class)
class ChannelTransportTest {
@Test
fun `should connect and list resources`(): Unit = runBlocking {
val serverOptions = ServerOptions(
capabilities = ServerCapabilities(
resources = ServerCapabilities.Resources(),
),
)
val server = Server(
Implementation(name = "test server", version = "1.0"),
serverOptions,
)
val (clientTransport, serverTransport) = ChannelTransport.createLinkedPair()
val client = Client(
clientInfo = Implementation(name = "test client", version = "1.0"),
)
val serverSessionResult = CompletableDeferred<ServerSession>()
listOf(
launch {
client.connect(clientTransport)
},
launch {
serverSessionResult.complete(server.createSession(serverTransport))
},
).joinAll()
val serverSession = serverSessionResult.await()
serverSession.setRequestHandler<InitializeRequest>(Method.Defined.Initialize) { _, _ ->
InitializeResult(
protocolVersion = LATEST_PROTOCOL_VERSION,
capabilities = ServerCapabilities(
resources = ServerCapabilities.Resources(null, null),
tools = ServerCapabilities.Tools(null),
),
serverInfo = Implementation(name = "test", version = "1.0"),
)
}
serverSession.setRequestHandler<ListResourcesRequest>(Method.Defined.ResourcesList) { _, _ ->
ListResourcesResult(
resources = listOf(
Resource(
uri = "/foo/bar",
name = "foo-bar-resource",
),
),
)
}
// These should not throw
client.listResources() shouldNotBeNull {
this.resources shouldHaveSize 1
}
client.close()
server.close()
}
}