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
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import io.kotest.matchers.string.shouldNotBeBlank
import io.modelcontextprotocol.kotlin.sdk.types.CallToolRequest
import io.modelcontextprotocol.kotlin.sdk.types.CallToolRequestParams
import io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities
import io.modelcontextprotocol.kotlin.sdk.types.ElicitationCompleteNotification
import io.modelcontextprotocol.kotlin.sdk.types.ElicitationCompleteNotificationParams
import io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequest
import io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequestParams
import io.modelcontextprotocol.kotlin.sdk.types.ListRootsRequest
Expand Down Expand Up @@ -77,6 +79,8 @@ class ClientConnectionTest : AbstractServerFeaturesTest() {
val loggingMessage = onClientNotification<LoggingMessageNotification>(Method.Defined.NotificationsMessage)
val resourceUpdated =
onClientNotification<ResourceUpdatedNotification>(Method.Defined.NotificationsResourcesUpdated)
val elicitationComplete =
onClientNotification<ElicitationCompleteNotification>(Method.Defined.NotificationsElicitationComplete)

init {
onClientRequest<ListRootsRequest, ListRootsResult>(Method.Defined.RootsList) {
Expand Down Expand Up @@ -109,6 +113,12 @@ class ClientConnectionTest : AbstractServerFeaturesTest() {
withClue("sendResourceUpdated() delivered wrong URI") {
resourceUri shouldBe "test://res"
}
withClue("sendElicitationComplete() did not deliver a notification to the client") {
elicitationComplete.isCompleted shouldBe true
}
withClue("sendElicitationComplete() delivered wrong elicitationId") {
elicitationComplete.await().params.elicitationId shouldBe "elicit-123"
}
capturedSessionId.shouldNotBeBlank()
}
}
Expand All @@ -128,6 +138,9 @@ class ClientConnectionTest : AbstractServerFeaturesTest() {
),
)
sendResourceUpdated(ResourceUpdatedNotification(ResourceUpdatedNotificationParams("test://res")))
sendElicitationComplete(
ElicitationCompleteNotification(ElicitationCompleteNotificationParams(elicitationId = "elicit-123")),
)
cap.sessionId.complete(sessionId)
}

Expand Down
60 changes: 60 additions & 0 deletions kotlin-sdk-core/api/kotlin-sdk-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -1417,6 +1417,65 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ElicitResult$Compani
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}

public final class io/modelcontextprotocol/kotlin/sdk/types/ElicitationCompleteNotification : io/modelcontextprotocol/kotlin/sdk/types/ServerNotification {
public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ElicitationCompleteNotification$Companion;
public fun <init> (Lio/modelcontextprotocol/kotlin/sdk/types/ElicitationCompleteNotificationParams;)V
public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/ElicitationCompleteNotificationParams;
public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/ElicitationCompleteNotificationParams;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitationCompleteNotification;
public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ElicitationCompleteNotification;Lio/modelcontextprotocol/kotlin/sdk/types/ElicitationCompleteNotificationParams;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitationCompleteNotification;
public fun equals (Ljava/lang/Object;)Z
public fun getMethod ()Lio/modelcontextprotocol/kotlin/sdk/types/Method;
public fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/ElicitationCompleteNotificationParams;
public synthetic fun getParams ()Lio/modelcontextprotocol/kotlin/sdk/types/NotificationParams;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}

public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/ElicitationCompleteNotification$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ElicitationCompleteNotification$$serializer;
public final fun childSerializers ()[Lkotlinx/serialization/KSerializer;
public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitationCompleteNotification;
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/types/ElicitationCompleteNotification;)V
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
}

public final class io/modelcontextprotocol/kotlin/sdk/types/ElicitationCompleteNotification$Companion {
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}

public final class io/modelcontextprotocol/kotlin/sdk/types/ElicitationCompleteNotificationParams : io/modelcontextprotocol/kotlin/sdk/types/NotificationParams {
public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ElicitationCompleteNotificationParams$Companion;
public fun <init> (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)V
public synthetic fun <init> (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Ljava/lang/String;
public final fun component2 ()Lkotlinx/serialization/json/JsonObject;
public final fun copy (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitationCompleteNotificationParams;
public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ElicitationCompleteNotificationParams;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitationCompleteNotificationParams;
public fun equals (Ljava/lang/Object;)Z
public final fun getElicitationId ()Ljava/lang/String;
public fun getMeta ()Lkotlinx/serialization/json/JsonObject;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}

public final synthetic class io/modelcontextprotocol/kotlin/sdk/types/ElicitationCompleteNotificationParams$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
public static final field INSTANCE Lio/modelcontextprotocol/kotlin/sdk/types/ElicitationCompleteNotificationParams$$serializer;
public final fun childSerializers ()[Lkotlinx/serialization/KSerializer;
public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitationCompleteNotificationParams;
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lio/modelcontextprotocol/kotlin/sdk/types/ElicitationCompleteNotificationParams;)V
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
}

public final class io/modelcontextprotocol/kotlin/sdk/types/ElicitationCompleteNotificationParams$Companion {
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}

public final class io/modelcontextprotocol/kotlin/sdk/types/ElicitationKt {
public static final fun ElicitRequestParams-EDEVuBg (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestFormParams;
public static synthetic fun ElicitRequestParams-EDEVuBg$default (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestParams$RequestedSchema;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequestFormParams;
Expand Down Expand Up @@ -2939,6 +2998,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/Method$Defined : jav
public static final field Initialize Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined;
public static final field LoggingSetLevel Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined;
public static final field NotificationsCancelled Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined;
public static final field NotificationsElicitationComplete Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined;
public static final field NotificationsInitialized Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined;
public static final field NotificationsMessage Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined;
public static final field NotificationsProgress Lio/modelcontextprotocol/kotlin/sdk/types/Method$Defined;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public sealed interface Method {
NotificationsToolsListChanged("notifications/tools/list_changed"),
NotificationsRootsListChanged("notifications/roots/list_changed"),
NotificationsPromptsListChanged("notifications/prompts/list_changed"),
NotificationsElicitationComplete("notifications/elicitation/complete"),
NotificationsTasksStatus("notifications/tasks/status"),
ToolsList("tools/list"),
ToolsCall("tools/call"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,36 @@ public data class RootsListChangedNotification(override val params: BaseNotifica
override val method: Method = Method.Defined.NotificationsRootsListChanged
}

// ============================================================================
// Elicitation Complete Notification
// ============================================================================

/**
* An optional notification from the server to the client,
* informing it of a completion of an out-of-band elicitation request.
*
* @property params Parameters identifying which elicitation completed.
*/
@Serializable
public data class ElicitationCompleteNotification(override val params: ElicitationCompleteNotificationParams) :
ServerNotification {
@EncodeDefault
override val method: Method = Method.Defined.NotificationsElicitationComplete
}

/**
* Parameters for a notifications/elicitation/complete notification.
*
* @property elicitationId The ID of the elicitation that completed.
* @property meta Optional metadata for this notification.
*/
@Serializable
public data class ElicitationCompleteNotificationParams(
val elicitationId: String,
@SerialName("_meta")
override val meta: JsonObject? = null,
) : NotificationParams

// ============================================================================
// Tools List Changed Notification
// ============================================================================
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ private val serverNotificationDeserializers: Map<String, DeserializationStrategy
Method.Defined.NotificationsResourcesListChanged.value to ResourceListChangedNotification.serializer(),
Method.Defined.NotificationsToolsListChanged.value to ToolListChangedNotification.serializer(),
Method.Defined.NotificationsPromptsListChanged.value to PromptListChangedNotification.serializer(),
Method.Defined.NotificationsElicitationComplete.value to ElicitationCompleteNotification.serializer(),
Method.Defined.NotificationsTasksStatus.value to TaskStatusNotification.serializer(),
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,51 @@ class NotificationTest {
)
}

@Test
fun `should serialize ElicitationCompleteNotification with meta`() {
val notification = ElicitationCompleteNotification(
ElicitationCompleteNotificationParams(
elicitationId = "elicit-42",
meta = buildJsonObject { put("source", "oauth-flow") },
),
)

verifySerialization(
notification,
McpJson,
"""
{
"method": "notifications/elicitation/complete",
"params": {
"elicitationId": "elicit-42",
"_meta": {
"source": "oauth-flow"
}
}
}
""".trimIndent(),
)
}

@Test
fun `should deserialize ElicitationCompleteNotification`() {
val json = """
{
"method": "notifications/elicitation/complete",
"params": {
"elicitationId": "elicit-99"
}
}
""".trimIndent()

val notification = verifyDeserialization<ElicitationCompleteNotification>(McpJson, json)
val params = notification.params

assertEquals(Method.Defined.NotificationsElicitationComplete, notification.method)
assertEquals("elicit-99", params.elicitationId)
assertNull(params.meta)
}

@Test
fun `should serialize TaskStatusNotification with all fields`() {
val notification = TaskStatusNotification(
Expand Down
3 changes: 3 additions & 0 deletions kotlin-sdk-server/api/kotlin-sdk-server.api
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public abstract interface class io/modelcontextprotocol/kotlin/sdk/server/Client
public static synthetic fun notification$default (Lio/modelcontextprotocol/kotlin/sdk/server/ClientConnection;Lio/modelcontextprotocol/kotlin/sdk/types/ServerNotification;Lio/modelcontextprotocol/kotlin/sdk/types/RequestId;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
public abstract fun ping (Lio/modelcontextprotocol/kotlin/sdk/types/PingRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static synthetic fun ping$default (Lio/modelcontextprotocol/kotlin/sdk/server/ClientConnection;Lio/modelcontextprotocol/kotlin/sdk/types/PingRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
public abstract fun sendElicitationComplete (Lio/modelcontextprotocol/kotlin/sdk/types/ElicitationCompleteNotification;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public abstract fun sendLoggingMessage (Lio/modelcontextprotocol/kotlin/sdk/types/LoggingMessageNotification;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public abstract fun sendPromptListChanged (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public abstract fun sendResourceListChanged (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
Expand Down Expand Up @@ -139,6 +140,7 @@ public class io/modelcontextprotocol/kotlin/sdk/server/Server {
public final fun removeResources (Ljava/util/List;)I
public final fun removeTool (Ljava/lang/String;)Z
public final fun removeTools (Ljava/util/List;)I
public final fun sendElicitationComplete (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/ElicitationCompleteNotification;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public final fun sendLoggingMessage (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/LoggingMessageNotification;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public final fun sendPromptListChanged (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public final fun sendResourceListChanged (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
Expand Down Expand Up @@ -182,6 +184,7 @@ public class io/modelcontextprotocol/kotlin/sdk/server/ServerSession : io/modelc
public final fun onClose (Lkotlin/jvm/functions/Function0;)V
public final fun onInitialized (Lkotlin/jvm/functions/Function0;)V
public final fun ping (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public final fun sendElicitationComplete (Lio/modelcontextprotocol/kotlin/sdk/types/ElicitationCompleteNotification;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public final fun sendLoggingMessage (Lio/modelcontextprotocol/kotlin/sdk/types/LoggingMessageNotification;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public final fun sendPromptListChanged (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public final fun sendResourceListChanged (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import io.modelcontextprotocol.kotlin.sdk.types.ElicitRequestFormParams
import io.modelcontextprotocol.kotlin.sdk.types.ElicitRequestParams
import io.modelcontextprotocol.kotlin.sdk.types.ElicitRequestURLParams
import io.modelcontextprotocol.kotlin.sdk.types.ElicitResult
import io.modelcontextprotocol.kotlin.sdk.types.ElicitationCompleteNotification
import io.modelcontextprotocol.kotlin.sdk.types.EmptyResult
import io.modelcontextprotocol.kotlin.sdk.types.ListRootsRequest
import io.modelcontextprotocol.kotlin.sdk.types.ListRootsResult
Expand Down Expand Up @@ -165,6 +166,13 @@ public interface ClientConnection {
* Sends a notification to the client indicating that the list of prompts has changed.
*/
public suspend fun sendPromptListChanged()

/**
* Sends a notification to the client indicating that an out-of-band elicitation has completed.
*
* @param notification Details of the completed elicitation.
*/
public suspend fun sendElicitationComplete(notification: ElicitationCompleteNotification)
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding a new abstract method to the public ClientConnection interface is a breaking change for any downstream implementors (source + binary). To keep this feature additive, provide a default implementation in the interface (e.g., delegate to notification(...)) or expose this as an extension/helper instead of an abstract method.

Suggested change
public suspend fun sendElicitationComplete(notification: ElicitationCompleteNotification)
public suspend fun sendElicitationComplete(notification: ElicitationCompleteNotification) {
notification(notification, null)
}

Copilot uses AI. Check for mistakes.
}

@Suppress("TooManyFunctions")
Expand Down Expand Up @@ -270,6 +278,11 @@ internal class ClientConnectionImpl(private val session: ServerSession) : Client
notification(PromptListChangedNotification())
}

override suspend fun sendElicitationComplete(notification: ElicitationCompleteNotification) {
logger.debug { "Sending elicitation complete notification for: ${notification.params.elicitationId}" }
notification(notification)
}

/**
* Determines whether a message with the specified logging level is accepted
* based on the current logging level of the session.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import io.modelcontextprotocol.kotlin.sdk.types.CreateMessageRequest
import io.modelcontextprotocol.kotlin.sdk.types.CreateMessageResult
import io.modelcontextprotocol.kotlin.sdk.types.ElicitRequestParams
import io.modelcontextprotocol.kotlin.sdk.types.ElicitResult
import io.modelcontextprotocol.kotlin.sdk.types.ElicitationCompleteNotification
import io.modelcontextprotocol.kotlin.sdk.types.EmptyJsonObject
import io.modelcontextprotocol.kotlin.sdk.types.EmptyResult
import io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequest
Expand Down Expand Up @@ -850,6 +851,16 @@ public open class Server(
public suspend fun sendPromptListChanged(sessionId: String) {
clientConnection(sessionId).sendPromptListChanged()
}

/**
* Triggers [ClientConnection.sendElicitationComplete] for session by provided [sessionId].
*
* @param sessionId The session ID to send the elicitation complete notification to.
* @param notification Details of the completed elicitation.
*/
public suspend fun sendElicitationComplete(sessionId: String, notification: ElicitationCompleteNotification) {
clientConnection(sessionId).sendElicitationComplete(notification)
}
// End the ServerSession / ClientConnection redirection section

// Start the notification handling section
Expand Down
Loading
Loading