Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions kotlin-sdk-client/api/kotlin-sdk-client.api
Original file line number Diff line number Diff line change
Expand Up @@ -66,14 +66,14 @@ public final class io/modelcontextprotocol/kotlin/sdk/client/SseClientTransport
public synthetic fun <init> (Lio/ktor/client/HttpClient;Ljava/lang/String;Lkotlin/time/Duration;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public synthetic fun <init> (Lio/ktor/client/HttpClient;Ljava/lang/String;Lkotlin/time/Duration;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun close (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun send (Lio/modelcontextprotocol/kotlin/sdk/types/JSONRPCMessage;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun send (Lio/modelcontextprotocol/kotlin/sdk/types/JSONRPCMessage;Lio/modelcontextprotocol/kotlin/sdk/shared/TransportSendOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun start (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}

public final class io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransport : io/modelcontextprotocol/kotlin/sdk/shared/AbstractTransport {
public fun <init> (Lkotlinx/io/Source;Lkotlinx/io/Sink;)V
public fun close (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun send (Lio/modelcontextprotocol/kotlin/sdk/types/JSONRPCMessage;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun send (Lio/modelcontextprotocol/kotlin/sdk/types/JSONRPCMessage;Lio/modelcontextprotocol/kotlin/sdk/shared/TransportSendOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun start (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}

Expand All @@ -83,8 +83,8 @@ public final class io/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClien
public fun close (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public final fun getProtocolVersion ()Ljava/lang/String;
public final fun getSessionId ()Ljava/lang/String;
public fun send (Lio/modelcontextprotocol/kotlin/sdk/types/JSONRPCMessage;Lio/modelcontextprotocol/kotlin/sdk/shared/TransportSendOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public final fun send (Lio/modelcontextprotocol/kotlin/sdk/types/JSONRPCMessage;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun send (Lio/modelcontextprotocol/kotlin/sdk/types/JSONRPCMessage;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static synthetic fun send$default (Lio/modelcontextprotocol/kotlin/sdk/client/StreamableHttpClientTransport;Lio/modelcontextprotocol/kotlin/sdk/types/JSONRPCMessage;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
public final fun setProtocolVersion (Ljava/lang/String;)V
public fun start (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import io.ktor.http.append
import io.ktor.http.isSuccess
import io.ktor.http.protocolWithAuthority
import io.modelcontextprotocol.kotlin.sdk.shared.AbstractTransport
import io.modelcontextprotocol.kotlin.sdk.shared.TransportSendOptions
import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage
import io.modelcontextprotocol.kotlin.sdk.types.McpJson
import kotlinx.coroutines.CancellationException
Expand Down Expand Up @@ -98,7 +99,7 @@ public class SseClientTransport(
}

@OptIn(ExperimentalCoroutinesApi::class)
override suspend fun send(message: JSONRPCMessage) {
override suspend fun send(message: JSONRPCMessage, options: TransportSendOptions?) {
check(initialized.load()) { "SseClientTransport is not initialized!" }
check(job?.isActive == true) { "SseClientTransport is closed!" }
check(endpoint.isCompleted) { "Not connected!" }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import io.github.oshai.kotlinlogging.KotlinLogging
import io.modelcontextprotocol.kotlin.sdk.internal.IODispatcher
import io.modelcontextprotocol.kotlin.sdk.shared.AbstractTransport
import io.modelcontextprotocol.kotlin.sdk.shared.ReadBuffer
import io.modelcontextprotocol.kotlin.sdk.shared.TransportSendOptions
import io.modelcontextprotocol.kotlin.sdk.shared.serializeMessage
import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage
import kotlinx.coroutines.CoroutineName
Expand Down Expand Up @@ -100,7 +101,7 @@ public class StdioClientTransport(private val input: Source, private val output:
}
}

override suspend fun send(message: JSONRPCMessage) {
override suspend fun send(message: JSONRPCMessage, options: TransportSendOptions?) {
if (!initialized.load()) {
error("Transport not started")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import io.ktor.http.contentType
import io.ktor.http.isSuccess
import io.ktor.utils.io.readUTF8Line
import io.modelcontextprotocol.kotlin.sdk.shared.AbstractTransport
import io.modelcontextprotocol.kotlin.sdk.shared.TransportSendOptions
import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCMessage
import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCNotification
import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCRequest
Expand Down Expand Up @@ -89,8 +90,8 @@ public class StreamableHttpClientTransport(
/**
* Sends a single message with optional resumption support
*/
override suspend fun send(message: JSONRPCMessage) {
send(message, null)
override suspend fun send(message: JSONRPCMessage, options: TransportSendOptions?) {
send(message, options?.resumptionToken, options?.onResumptionToken)
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.modelcontextprotocol.kotlin.sdk.client

import io.modelcontextprotocol.kotlin.sdk.shared.Transport
import io.modelcontextprotocol.kotlin.sdk.shared.TransportSendOptions
import io.modelcontextprotocol.kotlin.sdk.types.CallToolResult
import io.modelcontextprotocol.kotlin.sdk.types.Implementation
import io.modelcontextprotocol.kotlin.sdk.types.InitializeResult
Expand All @@ -25,7 +26,7 @@ class MockTransport : Transport {

override suspend fun start() = Unit

override suspend fun send(message: JSONRPCMessage) {
override suspend fun send(message: JSONRPCMessage, options: TransportSendOptions?) {
mutex.withLock {
_sentMessages += message
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import io.modelcontextprotocol.kotlin.sdk.JSONRPCRequest
import io.modelcontextprotocol.kotlin.sdk.JSONRPCResponse
import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities
import io.modelcontextprotocol.kotlin.sdk.shared.Transport
import io.modelcontextprotocol.kotlin.sdk.shared.TransportSendOptions
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock

Expand All @@ -25,7 +26,7 @@ class OldSchemaMockTransport : Transport {

override suspend fun start() = Unit

override suspend fun send(message: JSONRPCMessage) {
override suspend fun send(message: JSONRPCMessage, options: TransportSendOptions?) {
mutex.withLock {
_sentMessages += message
}
Expand Down
43 changes: 33 additions & 10 deletions kotlin-sdk-core/api/kotlin-sdk-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,8 @@ public abstract class io/modelcontextprotocol/kotlin/sdk/shared/Protocol {
public final fun getRequestHandlers ()Ljava/util/Map;
public final fun getResponseHandlers ()Ljava/util/Map;
public final fun getTransport ()Lio/modelcontextprotocol/kotlin/sdk/shared/Transport;
public final fun notification (Lio/modelcontextprotocol/kotlin/sdk/types/Notification;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public final fun notification (Lio/modelcontextprotocol/kotlin/sdk/types/Notification;Lio/modelcontextprotocol/kotlin/sdk/types/RequestId;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static synthetic fun notification$default (Lio/modelcontextprotocol/kotlin/sdk/shared/Protocol;Lio/modelcontextprotocol/kotlin/sdk/types/Notification;Lio/modelcontextprotocol/kotlin/sdk/types/RequestId;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
public fun onClose ()V
public fun onError (Ljava/lang/Throwable;)V
public final fun removeNotificationHandler (Lio/modelcontextprotocol/kotlin/sdk/types/Method;)V
Expand Down Expand Up @@ -476,13 +477,13 @@ public final class io/modelcontextprotocol/kotlin/sdk/shared/RequestHandlerExtra
public fun <init> ()V
}

public final class io/modelcontextprotocol/kotlin/sdk/shared/RequestOptions {
public synthetic fun <init> (Lkotlin/jvm/functions/Function1;JILkotlin/jvm/internal/DefaultConstructorMarker;)V
public synthetic fun <init> (Lkotlin/jvm/functions/Function1;JLkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Lkotlin/jvm/functions/Function1;
public final fun component2-UwyO8pc ()J
public final fun copy-HG0u8IE (Lkotlin/jvm/functions/Function1;J)Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;
public static synthetic fun copy-HG0u8IE$default (Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/jvm/functions/Function1;JILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;
public final class io/modelcontextprotocol/kotlin/sdk/shared/RequestOptions : io/modelcontextprotocol/kotlin/sdk/shared/TransportSendOptions {
public synthetic fun <init> (Lio/modelcontextprotocol/kotlin/sdk/types/RequestId;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;JILkotlin/jvm/internal/DefaultConstructorMarker;)V
public synthetic fun <init> (Lio/modelcontextprotocol/kotlin/sdk/types/RequestId;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;JLkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component4 ()Lkotlin/jvm/functions/Function1;
public final fun component5-UwyO8pc ()J
public final fun copy-9VgGkz4 (Lio/modelcontextprotocol/kotlin/sdk/types/RequestId;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;J)Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;
public static synthetic fun copy-9VgGkz4$default (Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lio/modelcontextprotocol/kotlin/sdk/types/RequestId;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;JILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;
public fun equals (Ljava/lang/Object;)Z
public final fun getOnProgress ()Lkotlin/jvm/functions/Function1;
public final fun getTimeout-UwyO8pc ()J
Expand All @@ -495,16 +496,38 @@ public abstract interface class io/modelcontextprotocol/kotlin/sdk/shared/Transp
public abstract fun onClose (Lkotlin/jvm/functions/Function0;)V
public abstract fun onError (Lkotlin/jvm/functions/Function1;)V
public abstract fun onMessage (Lkotlin/jvm/functions/Function2;)V
public abstract fun send (Lio/modelcontextprotocol/kotlin/sdk/types/JSONRPCMessage;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public abstract fun send (Lio/modelcontextprotocol/kotlin/sdk/types/JSONRPCMessage;Lio/modelcontextprotocol/kotlin/sdk/shared/TransportSendOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static synthetic fun send$default (Lio/modelcontextprotocol/kotlin/sdk/shared/Transport;Lio/modelcontextprotocol/kotlin/sdk/types/JSONRPCMessage;Lio/modelcontextprotocol/kotlin/sdk/shared/TransportSendOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
public abstract fun start (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}

public final class io/modelcontextprotocol/kotlin/sdk/shared/Transport$DefaultImpls {
public static synthetic fun send$default (Lio/modelcontextprotocol/kotlin/sdk/shared/Transport;Lio/modelcontextprotocol/kotlin/sdk/types/JSONRPCMessage;Lio/modelcontextprotocol/kotlin/sdk/shared/TransportSendOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
}

public class io/modelcontextprotocol/kotlin/sdk/shared/TransportSendOptions {
public fun <init> ()V
public fun <init> (Lio/modelcontextprotocol/kotlin/sdk/types/RequestId;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V
public synthetic fun <init> (Lio/modelcontextprotocol/kotlin/sdk/types/RequestId;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestId;
public final fun component2 ()Ljava/lang/String;
public final fun component3 ()Lkotlin/jvm/functions/Function1;
public fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/RequestId;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/shared/TransportSendOptions;
public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/shared/TransportSendOptions;Lio/modelcontextprotocol/kotlin/sdk/types/RequestId;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/shared/TransportSendOptions;
public fun equals (Ljava/lang/Object;)Z
public final fun getOnResumptionToken ()Lkotlin/jvm/functions/Function1;
public final fun getRelatedRequestId ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestId;
public final fun getResumptionToken ()Ljava/lang/String;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}

public abstract class io/modelcontextprotocol/kotlin/sdk/shared/WebSocketMcpTransport : io/modelcontextprotocol/kotlin/sdk/shared/AbstractTransport {
public fun <init> ()V
public fun close (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
protected abstract fun getSession ()Lio/ktor/websocket/WebSocketSession;
protected abstract fun initializeSession (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun send (Lio/modelcontextprotocol/kotlin/sdk/types/JSONRPCMessage;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun send (Lio/modelcontextprotocol/kotlin/sdk/types/JSONRPCMessage;Lio/modelcontextprotocol/kotlin/sdk/shared/TransportSendOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun start (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,22 +93,58 @@ public val DEFAULT_REQUEST_TIMEOUT: Duration = 60.seconds

/**
* Options that can be given per request.
*
* @property relatedRequestId if present,
* `relatedRequestId` is used to indicate to the transport which incoming request to associate this outgoing message with.
* @property resumptionToken the resumption token used to continue long-running requests that were interrupted.
* This allows clients to reconnect and continue from where they left off, if supported by the transport.
* @property onResumptionToken a callback that is invoked when the resumption token changes, if supported by the transport.
* This allows clients to persist the latest token for potential reconnection.
* @property onProgress callback for progress notifications.
* If set, requests progress notifications from the remote end (if supported).
* When progress notifications are received, this callback will be invoked.
* @property timeout a timeout for this request.
* If exceeded, a McpException with code `RequestTimeout` will be raised from request().
* If not specified, `DEFAULT_REQUEST_TIMEOUT` will be used as the timeout.
*/
public data class RequestOptions(
/**
* If set, requests progress notifications from the remote end (if supported).
* When progress notifications are received, this callback will be invoked.
*/
val onProgress: ProgressCallback? = null,
public class RequestOptions(
relatedRequestId: RequestId? = null,
resumptionToken: String? = null,
onResumptionToken: ((String) -> Unit)? = null,
public val onProgress: ProgressCallback? = null,
public val timeout: Duration = DEFAULT_REQUEST_TIMEOUT,
) : TransportSendOptions(relatedRequestId, resumptionToken, onResumptionToken) {
public operator fun component4(): ProgressCallback? = onProgress
public operator fun component5(): Duration = timeout

public fun copy(
relatedRequestId: RequestId? = this.relatedRequestId,
resumptionToken: String? = this.resumptionToken,
onResumptionToken: ((String) -> Unit)? = this.onResumptionToken,
onProgress: ProgressCallback? = this.onProgress,
timeout: Duration = this.timeout,
): RequestOptions = RequestOptions(relatedRequestId, resumptionToken, onResumptionToken, onProgress, timeout)

override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || this::class != other::class) return false
if (!super.equals(other)) return false

other as RequestOptions

return onProgress == other.onProgress && timeout == other.timeout
}

/**
* A timeout for this request. If exceeded, an McpError with code `RequestTimeout`
* will be raised from request().
*
* If not specified, `DEFAULT_REQUEST_TIMEOUT` will be used as the timeout.
*/
val timeout: Duration = DEFAULT_REQUEST_TIMEOUT,
)
override fun hashCode(): Int {
var result = super.hashCode()
result = 31 * result + (onProgress?.hashCode() ?: 0)
result = 31 * result + timeout.hashCode()
return result
}

override fun toString(): String =
"RequestOptions(relatedRequestId=$relatedRequestId, resumptionToken=$resumptionToken, onResumptionToken=$onResumptionToken, onProgress=$onProgress, timeout=$timeout)"
}

/**
* Extra data given to request handlers.
Expand Down Expand Up @@ -456,11 +492,9 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio
),
)

val serialized = JSONRPCNotification(
notification.method.value,
params = McpJson.encodeToJsonElement(notification),
)
transport.send(serialized)
val jsonRpcNotification = notification.toJSON()

transport.send(jsonRpcNotification, options)

result.completeExceptionally(reason)
}
Expand All @@ -469,7 +503,7 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio
try {
withTimeout(timeout) {
logger.trace { "Sending request message with id: $jsonRpcRequestId" }
this@Protocol.transport?.send(jsonRpcRequest)
this@Protocol.transport?.send(jsonRpcRequest, options)
}
return result.await()
} catch (cause: TimeoutCancellationException) {
Expand All @@ -489,13 +523,14 @@ public abstract class Protocol(@PublishedApi internal val options: ProtocolOptio
/**
* Emits a notification, which is a one-way message that does not expect a response.
*/
public suspend fun notification(notification: Notification) {
public suspend fun notification(notification: Notification, relatedRequestId: RequestId? = null) {
logger.trace { "Sending notification: ${notification.method}" }
val transport = this.transport ?: error("Not connected")
assertNotificationCapability(notification.method)
val sendOptions = relatedRequestId?.let { TransportSendOptions(relatedRequestId = it) }
val jsonRpcNotification = notification.toJSON()

val message = notification.toJSON()
transport.send(message)
transport.send(jsonRpcNotification, sendOptions)
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,12 @@ public interface Transport {

/**
* Sends a JSON-RPC message (request or response).
*
* @property message The JSON-RPC message to send, either a request or a response.
* @property options Optional transport-specific options that control sending behavior.
* Different transport implementations may support different options.
*/
public suspend fun send(message: JSONRPCMessage)
public suspend fun send(message: JSONRPCMessage, options: TransportSendOptions? = null)

/**
* Closes the connection.
Expand Down
Loading
Loading