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
6 changed files with 118 additions and 15 deletions

View file

@ -43,6 +43,11 @@ public class io/modelcontextprotocol/kotlin/sdk/client/Client : io/modelcontextp
public static synthetic fun unsubscribeResource$default (Lio/modelcontextprotocol/kotlin/sdk/client/Client;Lio/modelcontextprotocol/kotlin/sdk/types/UnsubscribeRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
}
public final class io/modelcontextprotocol/kotlin/sdk/client/ClientKt {
public static final fun mcpClient (Lio/modelcontextprotocol/kotlin/sdk/types/Implementation;Lio/modelcontextprotocol/kotlin/sdk/client/ClientOptions;Lio/modelcontextprotocol/kotlin/sdk/shared/Transport;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static synthetic fun mcpClient$default (Lio/modelcontextprotocol/kotlin/sdk/types/Implementation;Lio/modelcontextprotocol/kotlin/sdk/client/ClientOptions;Lio/modelcontextprotocol/kotlin/sdk/shared/Transport;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
}
public final class io/modelcontextprotocol/kotlin/sdk/client/ClientOptions : io/modelcontextprotocol/kotlin/sdk/shared/ProtocolOptions {
public fun <init> ()V
public fun <init> (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities;Z)V

View file

@ -1,6 +1,7 @@
package io.modelcontextprotocol.kotlin.sdk.client
import io.github.oshai.kotlinlogging.KotlinLogging
import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi
import io.modelcontextprotocol.kotlin.sdk.shared.Protocol
import io.modelcontextprotocol.kotlin.sdk.shared.ProtocolOptions
import io.modelcontextprotocol.kotlin.sdk.shared.RequestOptions
@ -69,6 +70,30 @@ public class ClientOptions(
enforceStrictCapabilities: Boolean = true,
) : ProtocolOptions(enforceStrictCapabilities = enforceStrictCapabilities)
/**
* Initializes and connects an MCP client using the provided clientInfo [Implementation], client options,
* and transport mechanism.
*
* @param clientInfo The implementation details of the MCP client, including its name, version, and other metadata.
* @param clientOptions Optional client configuration settings, such as supported capabilities
* and strict enforcement options. Defaults to a new instance of [ClientOptions].
* @param transport The transport mechanism used for communication.
* @return An instance of [Client] that is connected and ready for use with the specified transport.
*/
@ExperimentalMcpApi
public suspend fun mcpClient(
clientInfo: Implementation,
clientOptions: ClientOptions = ClientOptions(),
transport: Transport,
): Client {
val client = Client(
clientInfo = clientInfo,
options = clientOptions,
)
client.connect(transport)
return client
}
/**
* An MCP client on top of a pluggable transport.
*

View file

@ -130,6 +130,9 @@ public final class io/modelcontextprotocol/kotlin/sdk/ErrorCode$Unknown$Companio
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
public abstract interface annotation class io/modelcontextprotocol/kotlin/sdk/ExperimentalMcpApi : java/lang/annotation/Annotation {
}
public final class io/modelcontextprotocol/kotlin/sdk/InitializedNotification {
public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/InitializedNotification;
public final fun Params (Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/BaseNotificationParams;

View file

@ -0,0 +1,54 @@
package io.modelcontextprotocol.kotlin.sdk
import kotlin.annotation.AnnotationTarget.ANNOTATION_CLASS
import kotlin.annotation.AnnotationTarget.CLASS
import kotlin.annotation.AnnotationTarget.CONSTRUCTOR
import kotlin.annotation.AnnotationTarget.FIELD
import kotlin.annotation.AnnotationTarget.FUNCTION
import kotlin.annotation.AnnotationTarget.LOCAL_VARIABLE
import kotlin.annotation.AnnotationTarget.PROPERTY
import kotlin.annotation.AnnotationTarget.PROPERTY_GETTER
import kotlin.annotation.AnnotationTarget.PROPERTY_SETTER
import kotlin.annotation.AnnotationTarget.TYPEALIAS
import kotlin.annotation.AnnotationTarget.VALUE_PARAMETER
/**
* Annotation marking an API as experimental and subject to changes or removal in the future.
*
* This annotation is used to signal that a particular element, such as a class, function, or property,
* is part of an experimental API. Such APIs may not be stable, and their usage requires opting in
* explicitly.
*
* Users of the annotated API must explicitly accept the opt-in requirement to ensure they are aware
* of the potential instability or unfinished nature of the API.
*
* Targets that can be annotated include:
* - Classes
* - Annotation classes
* - Properties
* - Fields
* - Local variables
* - Value parameters
* - Constructors
* - Functions
* - Property getters
* - Property setters
* - Type aliases
*/
@RequiresOptIn
@MustBeDocumented
@Target(
CLASS,
ANNOTATION_CLASS,
PROPERTY,
FIELD,
LOCAL_VARIABLE,
VALUE_PARAMETER,
CONSTRUCTOR,
FUNCTION,
PROPERTY_GETTER,
PROPERTY_SETTER,
TYPEALIAS,
)
@Retention(AnnotationRetention.BINARY)
public annotation class ExperimentalMcpApi

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)
}