Add experimental mcpClient(...) factory method (#396)

- Introduced `@ExperimentalMcpApi` annotation to mark experimental APIs.
- Added `mcpClient` function for initializing and connecting MCP
clients.
- Modified tests to utilize the new `mcpClient` function.
- Refactored variable naming in transport tests for improved
readability.

## Motivation and Context
To improve the creation of the MCP Client experience.

## How Has This Been Tested?
Integration test updated

## Breaking Changes
No

## Types of changes
<!-- What types of changes does your code introduce? Put an `x` in all
the boxes that apply: -->
- [ ] 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)
- [ ] 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
- [ ] I have added appropriate error handling
- [ ] I have added or updated documentation as needed

## Additional context
<!-- Add any other context, implementation notes, or design decisions
-->
This commit is contained in:
Konstantin Pavlov 2025-11-12 20:53:25 +02:00 • committed by GitHub
commit 93d544e618
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 118 additions and 15 deletions

View file

@ -17,12 +17,12 @@ class StdioClientTransportTest : BaseTransportTest() {
val input = process.inputStream.asSource().buffered()
val output = process.outputStream.asSink().buffered()
val client = StdioClientTransport(
val transport = StdioClientTransport(
input = input,
output = output,
)
testTransportOpenClose(client)
testTransportOpenClose(transport)
process.destroy()
}
@ -35,12 +35,12 @@ class StdioClientTransportTest : BaseTransportTest() {
val input = process.inputStream.asSource().buffered()
val output = process.outputStream.asSink().buffered()
val client = StdioClientTransport(
val transport = StdioClientTransport(
input = input,
output = output,
)
testTransportRead(client)
testTransportRead(transport)
process.waitFor()
process.destroy()
@ -55,12 +55,12 @@ class StdioClientTransportTest : BaseTransportTest() {
val input = process.inputStream.asSource().buffered()
val output = process.outputStream.asSink().buffered()
val client = StdioClientTransport(
val transport = StdioClientTransport(
input = input,
output = output,
)
testTransportRead(client)
testTransportRead(transport)
process.waitFor()
process.destroy()

View file

@ -1,14 +1,18 @@
package io.modelcontextprotocol.kotlin.sdk.server
import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi
import io.modelcontextprotocol.kotlin.sdk.Implementation
import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities
import io.modelcontextprotocol.kotlin.sdk.client.Client
import io.modelcontextprotocol.kotlin.sdk.client.ClientOptions
import io.modelcontextprotocol.kotlin.sdk.client.mcpClient
import io.modelcontextprotocol.kotlin.sdk.shared.InMemoryTransport
import io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertNull
import kotlin.test.assertEquals
@OptIn(ExperimentalMcpApi::class)
class ServerInstructionsTest {
@Test
@ -22,10 +26,17 @@ class ServerInstructionsTest {
// The instructions should be stored internally and used in handleInitialize
// We can't directly access the private field, but we can test it through initialization
val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair()
val client = Client(clientInfo = Implementation(name = "test client", version = "1.0"))
server.createSession(serverTransport)
client.connect(clientTransport)
val client = mcpClient(
clientInfo = Implementation(name = "test client", version = "1.0"),
clientOptions = ClientOptions(
capabilities = ClientCapabilities(
roots = ClientCapabilities.Roots(listChanged = false),
),
),
transport = clientTransport,
)
assertEquals(instructions, client.serverInstructions)
}
@ -41,10 +52,12 @@ class ServerInstructionsTest {
// The instructions should be stored internally and used in handleInitialize
// We can't directly access the private field, but we can test it through initialization
val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair()
val client = Client(clientInfo = Implementation(name = "test client", version = "1.0"))
server.createSession(serverTransport)
client.connect(clientTransport)
val client = mcpClient(
clientInfo = Implementation(name = "test client", version = "1.0"),
transport = clientTransport,
)
assertEquals(instructions, client.serverInstructions)
}
@ -58,10 +71,13 @@ class ServerInstructionsTest {
val server = Server(serverInfo, serverOptions)
val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair()
val client = Client(clientInfo = Implementation(name = "test client", version = "1.0"))
server.createSession(serverTransport)
client.connect(clientTransport)
val client = mcpClient(
clientInfo = Implementation(name = "test client", version = "1.0"),
transport = clientTransport,
)
assertNull(client.serverInstructions)
}