feat: add notification for elicitation complete (#667)

closes #546 

## How Has This Been Tested?
unit

## Breaking Changes
none

## Types of changes
- [ ] 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
- [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
This commit is contained in:
Pavel Gorgulov 2026-04-02 12:37:33 +02:00 • committed by GitHub
commit e5e5ec206f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 186 additions and 0 deletions

View file

@ -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
@ -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) {
@ -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()
}
}
@ -128,6 +138,9 @@ class ClientConnectionTest : AbstractServerFeaturesTest() {
),
)
sendResourceUpdated(ResourceUpdatedNotification(ResourceUpdatedNotificationParams("test://res")))
sendElicitationComplete(
ElicitationCompleteNotification(ElicitationCompleteNotificationParams(elicitationId = "elicit-123")),
)
cap.sessionId.complete(sessionId)
}

View file

@ -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;
@ -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;

View file

@ -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"),

View file

@ -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
// ============================================================================

View file

@ -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(),
)
}

View file

@ -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(

View file

@ -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;
@ -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;
@ -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;

View file

@ -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
@ -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)
}
@Suppress("TooManyFunctions")
@ -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.

View file

@ -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
@ -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

View file

@ -9,6 +9,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.Implementation
@ -432,5 +433,13 @@ public open class ServerSession(
* Sends a notification to the client indicating that the list of prompts has changed.
*/
public suspend fun sendPromptListChanged(): Unit = clientConnection.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): Unit =
clientConnection.sendElicitationComplete(notification)
// End the ClientConnection redirection section
}