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:
parent
8bdbd36c32
commit
93d544e618
6 changed files with 118 additions and 15 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue